" @wookayin's vimrc file
" https://dotfiles.wook.kr/vim/vimrc
"
" https://github.com/wookayin/dotfiles

scriptencoding utf-8

" Allow lazy execution of plugin inits (after VimEnter and then UI initializes)
autocmd! VimEnter
autocmd! User LazyInit
if exists("*timer_start") && exists('*nvim_command')  " neovim
  autocmd! VimEnter * call timer_start(0, { -> nvim_command('doautocmd User LazyInit') })
else  " No timer support, this will run during VimEnter (before UI draws)
  autocmd! VimEnter * call execute('doautocmd User LazyInit')
endif


"""""""""""""""""""""""""""""""""""""""""
" 0. Load Plugin {{{
"""""""""""""""""""""""""""""""""""""""""

" All the vim plugins are managed by 'vim-plug'
" and listed up in the separate file 'plugins.vim',
" so that this vimrc would still work standalone without plugins.
let g:plugs = {}
if filereadable(expand('$HOME/.vim/plugins.vim')) && !has('nvim')
  " Note: on neovim, we will switch to lazy.vim (see init.lua).
  " For the time being neovim <= 0.7.x will have limited support with vim-plug
  " during the gradual transition period.
  source $HOME/.vim/plugins.vim
endif

" Functions to be executed for each plugin BEFORE the plugin loads.
let g:PlugConfig = {}

" private helper functions
function! s:list_plugs() abort
  if has('nvim-0.8')   " lazy.nvim
    return luaeval('require "config/plugins".list_plugs()')
  else
    return keys(g:plugs)
  end
endfunction

" }}}
"""""""""""""""""""""""""""""""""""""""""
" 1. General Settings {{{
"""""""""""""""""""""""""""""""""""""""""

filetype plugin on
filetype indent on

if !has('nvim') && !exists('g:syntax_on')
  " Use syntax. Note: neovim automatically turns syntax on *AFTER* sourcing init.lua is complete
  " (see :startup, step 9), so there's no need to turn it on for neovim. Actually, the order matters;
  " `syntax on` should NOT be called when some syntax plugins are lazy-loaded (folke/lazy.nvim#775).
  " See $VIMRUNTIME/syntax/synload.vim and $VIMRUNTIME/syntax/syntax.vim
  syntax on
  let g:syntax_on = 1
end

set nocompatible

if !empty($SHELL) && filereadable($SHELL)
  let &g:shell=$SHELL
elseif filereadable('/bin/zsh')
  set shell=/bin/zsh
else
  set shell=/bin/bash
endif

" use path '~/.vim' even on non-unix machine
set runtimepath+=~/.vim

" Add ~/.local/bin and ~/.dotfiles/bin to $PATH even if haven't (e.g., using bash shell)
" See ~/.zshenv for a proper $PATH configuration.
function! s:ensure_PATH(dir) abort
  let l:dir = expand(a:dir)
  if match(":" . $PATH . ":", ":" . l:dir, ":") < 0
    let $PATH = l:dir . ":" . $PATH
  endif
endfunction
call s:ensure_PATH('~/.local/bin')
call s:ensure_PATH('~/.dotfiles/bin')

" load plugins with pathogen
try
  runtime bundle/vim-pathogen/autoload/pathogen.vim
  call pathogen#infect()
catch
endtry

" basic displays
set number                  " show line numbers
set ruler

" input settings
set backspace=indent,eol,start     " allow backspaces over everything
set autoindent
set smartindent
set pastetoggle=<F8>

set nowrap
set textwidth=0             " disable automatic line breaking
set cursorline

" tab settings
if has('vim_starting')
  set tabstop=4
  set shiftwidth=4
  set softtabstop=4
  set shiftround
endif

" tab navigation
set showtabline=2           " always show tab pannel

set scrolloff=3
set sidescrolloff=3

if has('nvim')
  augroup TermScrollFix
    autocmd!
    " Disable scrolloff in terminal buffers (see neovim/neovim#11915)
    autocmd TermOpen,TermEnter * setlocal scrolloff=0 sidescrolloff=0
    autocmd TermOpen           * setlocal signcolumn=no
  augroup end
endif

" Use signcolumn for diagnostics and gitsigns. See :Config git, :Config lsp
if has('nvim')
  set signcolumn=yes:1
endif

" search
set ignorecase              " case-insensitive by default
set smartcase               " case-sensitive if keyword contains both uppercase and lowercase
set incsearch
set hlsearch

if has('nvim')
  " live preview of substitute command, with a split window
  " @seealso http://vimcasts.org/episodes/neovim-eyecandy/
  set inccommand=split
endif

" more sensible jump behavior
if has('nvim-0.8')
  " Use browser-style (or 'tagstack') navigation (see :help jumplist-stack)
  set jumpoptions+=stack
  " Preserve "view" such as relative cursor position to window (see :help mark-view)
  set jumpoptions+=view
endif

" Fold level: when starting, let all the folds be open
set nofoldenable

" When jumping to line, folds on the line should be opened (:help 'foldopen')
set foldopen+=jump

" foldcolumn display
if has('nvim-0.5.0')
  set foldcolumn=auto
endif

" use spaces for tabbing, by default
set expandtab

" vertical splits
set fillchars+=vert:│

" listchars for whitespaces
set list
set listchars=tab:»\ ,trail:·,extends:>,precedes:<

augroup listchars_filetype
  autocmd!
  autocmd FileType GV setlocal listchars-=trail:·
augroup END

" wildmenu settings
set wildmenu
try
  " neovim 0.4.0+: popupmenu completion in the cmdline mode
  set wildoptions+=pum
  set wildmode=longest:full,full

  " make <up>, <down>, <tab>, <shift-tab> work well with popupmenu in cmdline
  " (Note) this setting will be overriden later for wilder.nvim
  cnoremap <expr> <up>   pumvisible() ? "<C-p>" : "<up>"
  cnoremap <expr> <down> pumvisible() ? "<C-n>" : "<down>"
catch
  " old wildmenu heavior (vanilla vim)
  set wildmode=list,longest:full
endtry
set wildignore=*.swp,*.swo,*.class,*.pyc,__pycache__,

" status line
" Neovim(0.8+): lualine will override with laststatus=3
" see ~/.config/nvim/lua/config/statusline.lua
if !has('nvim')
  set laststatus=2            " show anytime (legacy vim behavior)
endif

set noshowmode              " don't display mode, e.g. '-- INSERT --'
set showcmd

" title: turn on (for tmux and terminal apps tab title)
set title

" customize the native statusline, just in case airline/lualine is not available
" (this setting will be replaced afterwards by airline/lualine)
if empty(&statusline)
  set statusline=%1*%{winnr()}\ %*%<\ %f\ %h%m%r%=%l,%c%V\ (%P)
endif

" mouse behaviour: in all modes (normal, visual, and insert)
" Note: To temporarily disable mouse support, hold the shift or alt key while using the mouse.
if has('mouse')
  set mouse=a
endif
if ! has('nvim')
  " vim only (not in neovim)
  set ttymouse=xterm2
endif


" encoding and line ending settings
if !has('nvim')
  set encoding=utf-8
endif
set fileencodings=utf-8,cp949,latin1
set fileformats=unix,dos

" split and autocomplete settings
set splitbelow                              " :split  opens window below (:belowright split), including preview windows
set splitright                              " :vsplit opens window right (:belowright vsplit)
set completeopt=menuone,preview,longest     " show preview and pop-up menu

if has('nvim-0.9')
  set splitkeep=screen      " Keep the text on the same screen line.
endif

" Avoid 'Pattern not found' messages (nvim-compe, etc.)
set shortmess+=c

" No intro message when starting vim
set shortmess+=I

" no fucking swap and backup files
set noswapfile
set nobackup

" dictionary
if filereadable('/usr/share/dict/words')
  set dictionary+=/usr/share/dict/words
endif

" Retain more history (:, search strings, etc.)
set history=10000
set undolevels=1000
if has('nvim')
  " ': Keep 10000 oldfiles (:h 'shada')
  " <: Maximum number of lines saved for each register.
  " default: set shada=!,'100,<50,s10,h
  set shada=!,'10000,<100,s10,h
endif

" miscellanious
set visualbell

" lazyredraw: no redrawing during macro execution
" This should NOT be set during startup if < 0.9.1, see neovim/neovim#23534
if has('nvim')
  lua vim.defer_fn(function() vim.opt.lazyredraw = true; end, 0)
else  " vanilla vim
  set lazyredraw
endif

" the timeout value for CursorHold.
set updatetime=1000

set matchpairs+=<:>

" Note: the built-in plugin matchparen is very slow,
" (especially Highlight_Matching_Pair) and block the UI.
" We can use shorter timeout value (default is 300ms)
let g:matchparen_timeout = 10

" when launching files via quickfix, FZF, or something else,
" first switch to existing tab (if any) that contains the target buffer,
" or open a new buffer by splitting window in the current tab otherwise.
set switchbuf+=usetab,split

" diff options (&diffopt)
try
  " Ignore whitespaces
  set diffopt+=iwhite
  " Use more intuitive, semantically easy-to-parse diff algorithm
  if has('patch-8.1.0360') || has('nvim')
    set diffopt+=internal,algorithm:patience
  endif
  " Enhance diff result by line matching algorithm (neovim/neovim#14537)
  if has('nvim-0.9.0')
    set diffopt+=linematch:60
  endif
catch   " ignore if diffopt unknown for legacy vimdiff
endtry

" jump to the last position when reopening a file
if has('autocmd')
  let s:last_position_disable_filetypes = ['gitcommit']
  au BufReadPost * if index(s:last_position_disable_filetypes, &ft) < 0 && line("'\"") > 1 && line("'\"") <= line("$") | exe "normal! g'\"zz" | endif
endif

" use strong encryption
if ! has('nvim')
  if v:version >= 800 || v:version == 704 && has('patch399')
    set cryptmethod=blowfish2 " Requires >= 7.4.399
  else
    set cryptmethod=blowfish
  endif
endif

" When opening http urls using netrw, follow 302 redirects
let g:netrw_http_cmd = 'curl -L -o'

" errorformat (for parsing compiler output into quickfix)
" use "%t" (warning: ..., error: ...) so that errormarker.vim
" can show diagnostics with proper level (e.g. show warnings as warning)
let &errorformat="%f:%l:%c: %t%*[^:]:%m," . "%f:%l: %t%*[^:]:%m," . &errorformat

" terminal mode (neovim)
if has('nvim')
  au TermOpen * setlocal nonumber norelativenumber
endif

" Use nvr as git editor (inside neovim terminal)
" nvr >= 2.5.0 required.
" One exception: on a floaterm, nvr should not be used. However,
" floaterm does not allow customization of environment variables.
" As a workaround, we unset $GIT_EDITOR in ~/.zshenv.
if has('nvim') && executable('nvr')
  let $GIT_EDITOR = 'nvr -cc split --remote-wait'
  autocmd FileType gitcommit set bufhidden=delete
endif

" For debugging
function! ToggleVerbose()
  if !&verbose
    set verbosefile=~/.vim/verbose.log
    set verbose=15
  else
    set verbose=0
    set verbosefile=
  endif
endfunction

" :lua vim.notify / VimNotify
if has('nvim') && luaeval('vim.notify == nil')
  " Backward compatibility for neovim < 0.5
  " see nvim-notify plugin settings below
  lua vim.notify = function(msg, log_level, opts) print(msg) end
endif

if has('nvim')
  function! VimNotify(msg, ...)
    " Compatible with vanilla vim, neovim 0.4.x, and 0.5.0+ (native)
    " Same spec as v:lua.vim.notify(msg, log, opts).
    let l:log_level = a:0 >= 1 ? a:000[0] : 'info'
    let l:opts = a:0 >= 2 ? a:000[1] : {}
    return luaeval("vim.notify(_A[1], _A[2], _A[3])", [a:msg, l:log_level, l:opts])
  endfunction
else  " fallback for vanilla vim
  function! VimNotify(msg, ...)
    echom a:msg
  endfunction
endif
command! -nargs=+ Notify         :call VimNotify(<q-args>)
command! -nargs=+ NotifyError    :call VimNotify(<q-args>, 'error')

" }}}
"""""""""""""""""""""""""""""""""""""""""
" 2. Key and Functional Mappings {{{
"""""""""""""""""""""""""""""""""""""""""

" the leader key
" (NOTE) leader key is mapped to vim-which-key, see sections below
let mapleader=','           " comma is the <Leader> key.
let maplocalleader=','      " comma : <LocalLeader>
nnoremap <leader>:  :

nnoremap <Space> :

inoremap <silent> <C-k>       <Cmd>stopinsert<CR><Cmd>update<CR>
nnoremap          <leader>e   <Cmd>e<CR>zR

nnoremap Q <Nop>
nnoremap QQ ZQ

" navigation key mapping
noremap <silent> k gk
noremap <silent> j gj
sunmap k
sunmap j

nmap <up> gk
nmap <down> gj

inoremap <up> <c-\><c-o>gk
inoremap <down> <c-\><c-o>gj

noremap <C-F> <C-D>
noremap <C-B> <C-U>

" Ignore errornous input in Mac OS X
imap <D-space> <Nop>

" Workaround neovim bug (it freezes, why?)
imap <F3> <Nop>

" <Ctrl-Space> invokes <C-X><C-O> (omni-completion)
inoremap <C-Space> <C-x><C-o>
inoremap <C-@> <C-x><C-o>

" dd: do not yank an empty line into the default register
if has('nvim-0.7')
lua << EOF
vim.keymap.set('n', 'dd', function()
  if vim.api.nvim_get_current_line():match("^%s*$") then
    return '"_dd'
  else
    return "dd"
  end
end, { expr = true, desc = 'dd (do not yank empty line)'})
EOF
endif

" window navigation
nnoremap <C-h> <C-w>h
nnoremap <C-j> <C-w>j
nnoremap <C-k> <C-w>k
nnoremap <C-l> <C-w>l

nnoremap <M-h> <C-w>h
nnoremap <M-j> <C-w>j
nnoremap <M-k> <C-w>k
nnoremap <M-l> <C-w>l

" window navigation: aware of tmux
let g:tmux_navigator_no_mappings = 1
let g:PlugConfig['vim-tmux-navigator'] = 's:init_tmux_navigator'
function! s:init_tmux_navigator() abort
  if !empty($TMUX)
    nnoremap <silent> <c-h> <Cmd>TmuxNavigateLeft<cr>
    nnoremap <silent> <c-j> <Cmd>TmuxNavigateDown<cr>
    nnoremap <silent> <c-k> <Cmd>TmuxNavigateUp<cr>
    nnoremap <silent> <c-l> <Cmd>TmuxNavigateRight<cr>
  endif
endfunction

" window resize (simply)
nnoremap _ <C-W>-
nnoremap + <C-W>+
nnoremap <S-Right> <C-W>>
nnoremap <S-Left>  <C-W><
nnoremap <S-Down>  <C-W>-
nnoremap <S-Up>    <C-W>+

if exists(':tnoremap')
  tnoremap <S-Right> <cmd>wincmd ><CR>
  tnoremap <S-Left>  <cmd>wincmd <<CR>
  tnoremap <S-Down>  <cmd>wincmd -<CR>
  tnoremap <S-Up>    <cmd>wincmd +<CR>
endif


" zoom and unzoom (like tmux) -- by 'vim-maximizer'
nnoremap <C-w>z  <Cmd>MaximizerToggle<CR>
let g:maximizer_set_default_mapping = 0         " do not map <F3>

" well...?
inoremap <C-C> <ESC>


" Folding and unfolding
" see ~/.config/nvim/lua/config/folding.lua
let g:PlugConfig['nvim-ufo'] = 's:init_nvim_ufo'
function! s:init_nvim_ufo() abort
  " Need to disable zM, zR because it will change foldlevel
  " if executed before the keymap settings of nvim-ufo has been effective.
  nnoremap <plug>(nvim-ufo-not-ready)  <cmd>lua vim.schedule_wrap(vim.notify)(
        \ "nvim-ufo is yet to be initialized, please try again later...", "warn", { timeout = 500, title = "nvim-ufo" })<CR>
  nmap <silent> zM  <plug>(nvim-ufo-not-ready)
  nmap <silent> zR  <plug>(nvim-ufo-not-ready)
endfunction

" z<space> toggles folding.
" It will open the current fold recursively when fold is closed,
" or close (the narrow one) when fold is open at the cursor.
nnoremap <expr>  z<space>   foldclosed(line(".")) >= 0 ? "zO" : "zc"

function! s:safe_expand(arg) abort
  " When verbose is set, expand("<amatch>"), etc. will throw errors.
  " See vim-patch:8.2.4740 (or neovim a9e6cf0e)
  try
    return expand(a:arg)
  catch   " E495, E496, E497, E498, E1274, etc.
    return ''
  endtry
endfunction

" in terminal mode (neovim)
if has('nvim')
  " <C-\><C-n> is the key sequence for escaping form terminal mode
  " double <ESC> and double <C-\> also goes for escaping from terminal,
  " whereas single <ESC> would be directly passed inside the terminal.
  tnoremap <silent> <C-[><C-[> <C-\><C-n>
  tnoremap <silent> <C-\><C-\> <C-\><C-n>

  " Automatically enter insert mode when entering neovim terminal buffer
  augroup terminal_autoinsert
    autocmd!
    " There is a bug (or just a strange behavior?) of neovim where buffer options
    " (e.g. &buftype) gets updated late on WinEnter autocmds.
    " When a new window is opened, the new buffer that will be opened in the
    " new window will take effect *after* the WinEnter autocmd.
    " The actual buffer would not yet have been loaded at the time when
    " autocmd WinEnter events are being executed. As a result, the new window
    " will enter the insert mode even if it's not having a terminal buffer.
    " One known solution is to check if <amatch> is empty.
    " Credit: https://vi.stackexchange.com/questions/15966/strange-behaviour-of-autocmd
    autocmd WinEnter,BufWinEnter *
          \ if !empty(s:safe_expand('<amatch>')) && (&buftype == 'terminal')
          \ | startinsert | endif
    autocmd TermOpen *
          \ if nvim_buf_get_name(0) =~# '^term://'
          \ | startinsert | endif

    " mouse click puts into normal mode even in terminal; disable this
    autocmd TermOpen * tnoremap <buffer><silent> <LeftRelease> <Nop>
    autocmd TermOpen * tnoremap <buffer><silent> <LeftMouse> <cmd>startinsert<CR>
  augroup END

  " terminal in a new tab or vsplit buffer
  command! -nargs=*        -complete=shellcmd Term         :tabnew | :term <args>
  command! -nargs=*        -complete=shellcmd Ttab         :tabnew | :term <args>

  command! -nargs=* -count -complete=shellcmd TermSplit    :exec <q-mods> .' '. (<count> ? <count> : '').'split  | :term ' . <q-args>
  command! -nargs=* -count -complete=shellcmd TermVSplit   :exec <q-mods> .' '. (<count> ? <count> : '').'vsplit | :term ' . <q-args>

  command! -nargs=* -count -complete=shellcmd Tsplit       :exec <q-mods> .' '. (<count> ? <count> : '').'split  | :term ' . <q-args>
  command! -nargs=* -count -complete=shellcmd Tvsplit      :exec <q-mods> .' '. (<count> ? <count> : '').'vsplit | :term ' . <q-args>

  " Mapping for <c-hjkl> in terminal mode
  tmap <silent> <C-h> <C-\><C-n><cmd>wincmd h<CR>
  tmap <silent> <C-j> <C-\><C-n><cmd>wincmd j<CR>
  tmap <silent> <C-k> <C-\><C-n><cmd>wincmd k<CR>
  "tmap <silent> <C-l> <C-\><C-n><cmd>wincmd l<CR>   " Ctrl-L is clear screen
  tmap <silent> <M-h> <C-\><C-n><cmd>wincmd h<CR>
  tmap <silent> <M-j> <C-\><C-n><cmd>wincmd j<CR>
  tmap <silent> <M-k> <C-\><C-n><cmd>wincmd k<CR>
  tmap <silent> <M-l> <C-\><C-n><cmd>wincmd l<CR>

  " Ctrl-/ requires key remapping (neovim 0.8.0, see neovim/neovim#18735)
  tmap <c-/> <c-_>
endif

if has('nvim')
  " ignore erroneous key input on scrolling, <ffffffff>
  tnoremap <ScrollWheelLeft> <nop>
endif

" jump behavior: gF (follows the line number) by default
nnoremap gf  gF
nnoremap ]f  gF

" Buffer navigations
nnoremap [b  <Cmd>bprevious<CR>
nnoremap ]b  <Cmd>bnext<CR>
if has('nvim')
  tnoremap <silent> [b <Cmd>bprevious<CR>
  tnoremap <silent> ]b <Cmd>bnext<CR>
endif

" Tab navigations
nnoremap <silent> [t  <Cmd>tabprevious<CR>
nnoremap <silent> ]t  <Cmd>tabnext<CR>
if has('nvim')
  tnoremap <silent> [t <Cmd>tabprevious<CR>
  tnoremap <silent> ]t <Cmd>tabnext<CR>
endif

nnoremap <C-t>        <Cmd>tabnew<CR>
nnoremap <leader>tt   <Cmd>Tsplit<CR>

nnoremap <silent>   <C-S-tab> <Cmd>tabprevious<CR>
nnoremap <silent>   <C-tab>   <Cmd>tabnext<CR>
if has('nvim')
  tnoremap <silent> <C-S-tab> <Cmd>tabprevious<CR>
  tnoremap <silent> <C-tab>   <Cmd>tabnext<CR>
endif

" Handy tab navigations: <Alt-num>
nnoremap 1 1gt
nnoremap 2 2gt
nnoremap 3 3gt
nnoremap 4 4gt
nnoremap 5 5gt
nnoremap 6 6gt
nnoremap 7 7gt
nnoremap 8 8gt
nnoremap 9 9gt

" do not exit from visual mode when shifting
" (gv : select the preivous area)
vnoremap < <gv
vnoremap > >gv

" Locations
nnoremap [l  <Cmd>lprevious<CR>
nnoremap ]l  <Cmd>lnext<CR>

" ------------------
" Make and Build {{{
" ------------------
" We use <F5> and <Ctrl-F5> to quickly build and execute the code.
" <F5>   :Build        => Build and run the program.
" <C-F5> :Build        => Build and run the program. (same as <F5> for compatibility)
" <M-F5> :Debug        => Build and run the program in a debug mode (with DAP in the future) if available.

" To specialize project-wise, filetype-wise, or buffer-wise behaviors,
" the Build, Debug, and Output commands be overriden.
" However, we should NOT directly remap <F5> and <Ctrl-F5>.

" By default, <F5> runs :Build -> :Make. Ftplugins may define :Build and :Debug.
nmap <F5>    <cmd>silent w<CR><cmd>Build<CR>
nmap <C-F5>  <F5>
nmap <M-F5>  <cmd>silent w<CR><cmd>Debug<CR>
imap <F5>    <ESC><F5>a
imap <C-F5>  <ESC><C-F5>a


" Alternative to <F5> (shortcut)
nmap <leader>m <F5>

" Build commands default to Make
command! -nargs=* -bar Build    Make
command! -nargs=* -bar Debug    echohl WarningMsg | echon ":Debug not defined for this filetype. Try :Build instead." | echohl NONE

" :Make
" runs a &makeprg job asynchronously in the background.
"
" Note that filetype plugins or individual buffers may override the command :Make
" For instance, ~/.vim/after/ftplugin/python.vim  ~/.vim/after/ftplugin/cpp.vim
"
command! -nargs=* -complete=customlist,MakeCommandCompletion -bar Make
      \ call s:run_make("<bang>", <q-args>)
function! s:run_make(bang, qargs) abort
    " Asynchronous job dispatching in the background (output goes to quickfix)
    exec 'AsyncMake ' . a:qargs
    echohl Special
    echom Truncate('Make: ' . &makeprg . (empty(a:qargs) ? "" : " ") . a:qargs, v:echospace)
    echohl NONE
endfunction

" Completion for :Make commands
function! MakeCommandCompletion(A, L, P) abort
  if &makeprg == 'make'
    " credit: @pbnj https://dev.to/pbnj/how-to-get-make-target-tab-completion-in-vim-4mj1
    let l:targets = systemlist('make -qp | awk -F'':'' ''/^[a-zA-Z0-9][^$#\/\t=]*:([^=]|$)/ {split($1,A,/ /);for(i in A)print A[i]}'' | grep -v Makefile | sort -u')
    return filter(l:targets, 'v:val =~ "^' . a:A . '"')
  else
    return []
  end
endfunction

" A handy way to set (global) makeprg.
" See ~/.dotfiles/nvim/lua/config/commands/Makeprg.lua for nvim+lua version
command! -nargs=1 Makeprg       let &makeprg="<args>" | echo "&makeprg = <args>"
command! -nargs=1 MakeprgGlobal let &g:makeprg="<args>" | echo "&g:makeprg = <args>"
command! -nargs=1 MakeprgLocal  let &l:makeprg="<args>" | echo "&l:makeprg = <args>"


" }}}


" :OutputToggle or <F6> (Toggle quickfix)
" ---------------------------------------

" <F6>   :Output       => Show the output or build status window (quickfix, terminal, etc.)
map               <F6>   <Cmd>Output<CR>
imap              <F6>   <Cmd>Output<CR>
nnoremap <silent> <C-Q>  <Cmd>call QuickfixToggle()<CR>

" By default, :Output opens or closes the quickfix (copen) window
" filetype plugin or individual buffers may override the command :Output (or :COpen)
command! -nargs=0 -bar  Output          QuickfixToggle
if !has('nvim')
  command! -nargs=0 -bar  Output        botright copen
endif

" <leader><F6>: Copen no matter what  (note: <F6> might be overridden, e.g. neotest)
nnoremap <silent> <leader><F6>   <cmd>Copen<CR>

" <Alt-F6>: Copen vertically
nnoremap <silent> <M-F6>   <cmd>cclose<CR><cmd>vertical Copen 80<CR>

" <leader>L: show/close location list window
function! LocListToggle()
  let nr = winnr('$')
  :lopen
  let nr2 = winnr('$')
  if nr == nr2 | lclose | endif
endfunction
map  <silent> <leader>L <cmd>call LocListToggle()<CR>

" [F4] Next Error [Shift+F4] Previous Error
nnoremap <F4>   <cmd>cnext<CR>
nnoremap <S-F4> <cmd>cprevious<CR>

" [F2] save
inoremap <F2> <cmd>stopinsert<CR><cmd>w<CR>
noremap  <F2> <cmd>w<CR>

" save in the insert mode like in other editors
inoremap <C-S> <cmd>update<CR><cmd>redraw<CR>

" Sudo Save (:Wsudo command)
command! Wsudo w !sudo tee % > /dev/null


" Useful leader key combinations {{{

" <leader><space> : turn off search highlight
nmap <silent> <leader><space> <Cmd>noh<CR>


" CD: switch to the directory of the current buffer (or project root),
" per-tab (cmd = 'tcd'), per-window (cmd = 'lcd'), or globally (cmd = 'cd')
command! -nargs=0   CD   call CD_to(expand('%:p:h'), 'lcd')
function! CD_to(dir, cmd) abort
  if empty(a:dir) || a:dir == -1
    return
  endif
  " Use per-tab CWD.see :help :tcd, :lcd, etc.
  if index(['cd', 'tcd', 'lcd'], a:cmd) < 0 | echoerr "Invalid cmd: " . a:cmd | return | end
  execute printf('silent %s %s', a:cmd, a:dir)
  if has('nvim-0.8')
    call luaeval('vim.api.nvim_echo({
          \   { "[" .. _A[1] .. "] ", "Special" },
          \   { _A[2], "Directory" }
          \ }, true, {})
          \', [{'cd': 'global', 'tcd': 'tab', 'lcd': 'window'}[a:cmd], a:dir])
  else
    echohl Directory | echom printf('%s: %s', a:cmd, getcwd()) | echohl None
  endif

  " if NERDTree is open, chdir NERDTree as well
  " Note: neotree haves automatic 2-way cwd binding
  if exists('g:NERDTree') && g:NERDTree.IsOpen()
    :NERDTreeCWD | wincmd w
  endif
endfunction

function! DetermineProjectRoot(...) abort
  let l:arg = a:0 >= 1 ? a:000[0]: '%'
  " If the current file is under a git repository, that's it!
  let l:git_dir = s:get_git_workdir(l:arg)
  if !empty(l:git_dir)
    return l:git_dir
  endif

  " Not a git repository. Use heuristic and domain knowledge as much as you can
  let l:base_dir = expand(l:arg . ":p")
  if l:base_dir =~ 'python[^/]*/site-packages/[^/]\+/'
    return resolve(substitute(l:base_dir, '\(python[^/]*/site-packages/[^/]\+/\).*$', '\1', 'g'))
  else
    return ''
  endif
endfunction

" CDRoot: switch to the project root directory of the current buffer
command! -nargs=?   CDRoot  :call s:CDRoot(<f-args>)
nmap <silent> <leader>cd     <cmd>CDRoot<CR>
nmap <silent> <leader>lcd    <cmd>CDRoot lcd<CR>
function! s:CDRoot(...) abort
  let l:cd_cmd = a:0 >= 1 ? a:000[0] : 'tcd'

  let l:project_root = DetermineProjectRoot()
  if empty(l:project_root)
    echohl WarningMsg | echom 'Cannot determine project root directory. (Want :CD?)' | echohl None
  else
    return CD_to(l:project_root, l:cd_cmd)
  endif
endfunction


" <leader>R : screen sucks, redraw everything
function! Redraw()
  :mode
endfunction
nnoremap <silent> <leader>R :call Redraw()<CR>

" <leader>src : source ~/.vimrc
nnoremap <leader>src   <cmd>call _source_rc()<CR>
if v:vim_did_enter == 0   " Do not redefine the function
  function! _source_rc() abort
    if has('nvim')
      source ~/.config/nvim/init.lua
      doautocmd VimEnter
      echo 'Sourced ~/.config/nvim/init.lua'
    else
      source ~/.vimrc
      doautocmd VimEnter
      echo 'Sourced ~/.vimrc'
    endif
  endfunction
endif

" :Vimrc opens vim config files in a new tab
command! -nargs=0 Vimrc   call s:open_vimrc_tab()
function! s:open_vimrc_tab()
  let l:plug_width = max([5, float2nr(0.333 * &columns)])
  let l:addon_height = max([2, float2nr(0.1 * &lines)])

  exec ":tabnew " . resolve(expand("~/.vim/vimrc"))
  if filereadable(expand("~/.config/nvim/lua/config/lsp.lua"))
    exec ":vsplit " . resolve(expand("~/.config/nvim/lua/config/lsp.lua"))
    exec printf(":vertical resize %d", 2 * l:plug_width)
  endif
  if filereadable(expand("~/.vimrc.local"))
    exec ":botright sp " . resolve(expand("~/.vimrc.local"))
    exec printf(":resize %d", l:addon_height)
  endif

  exec ":botright vnew " . resolve(expand("~/.config/nvim/lua/config/plugins.lua"))
  exec printf(":vertical resize %d", l:plug_width)
  wincmd t  " focus on the first window (~/.vimrc)
endfunction

" :Plugins
command! -nargs=0 Plugins  exec ":Config plugins"

" <leader>{y,x,p} : {yank,cut,paste} wrt the system clipboard
" Note: on MacOS and windows, "* and "+ are the same (for the system clipboard)
" Note: on X11 (Linux), The register "* is the primary, "+ is the secondary,
"  but "+ is the one that works as the external clipboard without X11 or tmux
map <leader>y "+y
noremap <leader>x "+x
noremap <leader>p "+p

" <leader>w : save
nnoremap <leader>w <Cmd>w!<CR>

" <leader>q : quit/close window
nnoremap <silent> <leader>q <Cmd>q<CR>

" <leader>S : Strip trailing whitespaces
command! -nargs=0 Strip call StripTrailingWhitespaces()
nnoremap <leader>S <Cmd>Strip<CR>

" <leader>df : diffthis
nnoremap <leader>df <Cmd>diffthis<CR>

" Surround a word with quotes, single quotes, parens, brackets, braces, etc.
"   requires and powered by the plugin surround.vim :-)
" (Note) for visual blocks, use S command from surround.vim
nmap  <leader>s" ysiw"
nmap  <leader>s' ysiw'
nmap  <leader>s` ysiw`
nmap  <leader>s* ysiw*l
nmap  <leader>s_ ysiw_l
nmap  <leader>s~ ysiw~l
nmap  <leader>s$ ysiw$
nmap  <leader>s( ysiw(
nmap  <leader>s) ysiw)
nmap  <leader>s[ ysiw[
nmap  <leader>s] ysiw]
nmap  <leader>s{ ysiw{
nmap  <leader>s} ysiw}
" ask function: e.g., word -> function(word)
nmap  <leader>sf ysiwf
nmap  <leader>(  ysiwf

vmap  <leader>s" S"
vmap  <leader>s' S'
vmap  <leader>s` S`
vmap  <leader>s* S*
vmap  <leader>s_ S_
vmap  <leader>s~ S~
vmap  <leader>s$ S$
vmap  <leader>s( S(
vmap  <leader>s) S)
vmap  <leader>s[ S[
vmap  <leader>s] S]
vmap  <leader>s{ S{
vmap  <leader>s} S}
" ask function: e.g., word -> function(word)
vmap  <leader>sf Sf
vmap  <leader>(  Sf


" Zoom Tmux
noremap <silent> <leader>z :silent exec "!tmux resize-pane -Z"<CR>

" Prevent accidental <Ctrl-A> (on tmux) and <Ctrl-X>
if !empty($TMUX)
  nnoremap <C-a> <nop>
  nnoremap <C-x> <nop>
  " To increase/decrease numbers, press <comma>, <c-a>, a ...?
  nnoremap <leader><C-a> <C-a>
  nnoremap <leader><C-x> <C-x>
endif

" }}}

" }}}
"""""""""""""""""""""""""""""""""""""""""
" 3. More Functions and Commands {{{
"""""""""""""""""""""""""""""""""""""""""

" Utilities
if exists('*trim')
  function! Trim(input_string)
    return trim(a:input_string)  " builtin trim() if neovim 0.3.2+ or vim 8.0.1630+
  endfunction
else
  function! Trim(input_string)
    return substitute(a:input_string, '^\s*\(.\{-}\)\s*$', '\1', '')
  endfunction
endif

function! TrimRight(input_string)
  return substitute(a:input_string, '\s*$', '\1', '')
endfunction

function! Echo(msg) abort
  echo a:msg
  return a:msg
endfunction
function! Echom(msg) abort
  echom a:msg
  return a:msg
endfunction

function! Truncate(msg, length) abort
  if len(a:msg) >= a:length - 3
    return a:msg[:(a:length - 3)] . " ⋯"
  endif
  return a:msg
endfunction

if exists('*expandcmd')  " neovim 0.5, vim: +v8.1.1510
  function! ExpandCmd(string) abort
    return expandcmd(a:string)
  endfunction
else
  let s:expandable = '\\*\%(<\w\+>\|%\|#\d*\)\%(:[p8~.htre]\|:g\=s\(.\).\{-\}\1.\{-\}\1\)*'
  function! ExpandCmd(string) abort
    return substitute(a:string, s:expandable, '\=expand(submatch(0))', 'g')
  endfunction
endif

" Command aliases or abbrevations
" CommandAlias(aliasname, target, [register_cmd = v:false] | {options})
function! CommandAlias(aliasname, target, ...)
  let l:opts = get(a:000, 0, {})
  if type(l:opts) != type({})
    let l:opts = {'register_cmd' : l:opts}  " backward compatibility
  endif
  let l:register_cmd = get(l:opts, 'register_cmd', 0)

  " :aliasname => :target  (only applicable at the beginning of the command line)
  let l:target = a:target
  exec printf('cnoreabbrev <expr> %s ', a:aliasname)
    \ .printf('((getcmdtype() ==# ":" && getcmdline() =~# "^%s$") ? ', a:aliasname)
    \ .printf('("%s") : ("%s"))', escape(l:target, '"'), escape(a:aliasname, '"'))

  " optionally, make a dummy command to facilitate cmdline completion
  if l:register_cmd
    exec printf(':command! -nargs=* %s  <mods> %s <args>', a:aliasname, l:target)
  endif
endfunction
command! -nargs=+ CommandAlias call CommandAlias(<f-args>)

" some basic command abbrs
call CommandAlias('s%', 'source %')
call CommandAlias('tnew', 'tabnew')
call CommandAlias('E', 'e')
call CommandAlias('t', 'tab')  " we don't use :copy
call CommandAlias('v', 'vertical')  " we don't use :vglobal

call CommandAlias('vsb', 'vertical sb')

" WinDo: Like :windo, but preserves the current window and view.
function! WinDo(command) abort
  let l:currwin = winnr()
  let l:view = winsaveview()
  try
    execute printf('noautocmd windo execute "%s"', escape(a:command, '"'))
  finally
    noautocmd execute l:currwin . 'wincmd w'
    call winrestview(l:view)
  endtry
endfunction
command! -nargs=* WinDo   call WinDo(<q-args>)

" :Q -- close tab
function! s:Q() abort
  if tabpagenr('$') == 1
    qall
  else
    tabclose
  endif
endfunction
command! -nargs=0 Q   call s:Q()

" Lua utilities (helpful for debugging)
if has('nvim')
  " (nvim 0.7.0+) :lua= ... is equivalent as :lua print(vim.inspect(...))
  call CommandAlias('LuaEcho', 'lua=')
  call CommandAlias('luaecho', 'lua=')
  call CommandAlias('l', 'lua=')
  call CommandAlias('L', 'LuaREPL')

  " There is no ':require' command in vim; it is a common mistake with intending lua require...
  call CommandAlias('require', 'lua= require')

  " neorepl.nvim
  let g:loaded_neorepl = v:true   " no built-in commands (:Repl)
  command! -nargs=* -count=16 -complete=lua LuaREPL   call s:LuaREPL('<mods>', <count>, <q-args>)
  function! s:LuaREPL(mods, count, args) abort
    if a:mods == "vertical"
      exec printf("%s new | lua require('neorepl').new { lang = 'lua' }", a:mods)
    else
      exec printf("%s %dnew | lua require('neorepl').new { lang = 'lua' }", a:mods, a:count)
    endif
    if !empty(a:args)
      call nvim_input(a:args .. "<CR>")
    endif
  endfunction
  call CommandAlias('REPL', 'LuaREPL', v:true)
  call CommandAlias('Lua', 'LuaREPL', v:true)
endif


" ----------------------------------------------------------------------------
" <Leader>?/! : Google it / Feeling lucky
"   (code brought from @junegunn/dotfiles)
" ----------------------------------------------------------------------------
function! s:goog(pat, lucky)
  let q = '"'.substitute(a:pat, '["\n]', ' ', 'g').'"'
  let q = substitute(q, '[[:punct:] ]',
        \ '\=printf("%%%02X", char2nr(submatch(0)))', 'g')
  call system(printf('open "https://www.google.com/search?%sq=%s"',
        \ a:lucky ? 'btnI&' : '', q))
endfunction

nnoremap <leader>? :call <SID>goog(expand("<cword>"), 0)<cr>
nnoremap <leader>! :call <SID>goog(expand("<cword>"), 1)<cr>
xnoremap <leader>? "gy:call <SID>goog(@g, 0)<cr>gv
xnoremap <leader>! "gy:call <SID>goog(@g, 1)<cr>gv


" command abbrevations
" :eh, :vsh, :sph, :tabnewh (:th) => :{e, vs, sp, tabnew} %:h/
"  (to open files in the same directory as the current buffer)
function! EatWhitespace()
  let c = nr2char(getchar(0))
  " Invoke wilder manually because the completion is lost due to command-line abbrev.
  if exists('*wilder#start_from_normal_mode')
    call wilder#start_from_normal_mode()
  endif
  return index([nr2char(9), nr2char(10), nr2char(13), ' '], c) >= 0 ? '' : c
endfunction
call CommandAlias('eh', "e %:h/<C-R>=EatWhitespace()<CR>")
call CommandAlias('vsh', "vs %:h/<C-R>=EatWhitespace()<CR>")
call CommandAlias('sph', "sp %:h/<C-R>=EatWhitespace()<CR>")
call CommandAlias('tabnewh', "tabnew %:h/<C-R>=EatWhitespace()<CR>")
call CommandAlias('th',      "tabnew %:h/<C-R>=EatWhitespace()<CR>")

" :eplug, :vsplug => :e $VIMPLUG/, :vs $VIMPLUG/
call CommandAlias('eplug', "e $VIMPLUG/<C-R>=EatWhitespace()<CR>")
call CommandAlias('vsplug', "vs $VIMPLUG/<C-R>=EatWhitespace()<CR>")

" Command line mode mapping {{{
" Motion in the command line similar to the normal mode (:help tcsh-style)
cnoremap <C-a> <Home>
cnoremap <C-e> <End>
cnoremap <M-a> <Home>
cnoremap <M-e> <End>
cnoremap <M-0> <Home>
cnoremap <M-4> <End>

" move by one letter (h, l)
cnoremap <C-b> <Left>
cnoremap <C-f> <Right>
cnoremap <C-h> <Left>
cnoremap <C-l> <Right>
cnoremap <M-h> <Left>
cnoremap <M-l> <Right>

" move by word (b, w)
cnoremap <M-b> <S-Left>
cnoremap <M-w> <S-Right>

" }}}


" :Toggle... command aliases
if has('lambda') && exists('##CmdlineEnter')
  augroup RegisterToggleCommands
    autocmd!
    autocmd CmdlineEnter * call RegisterToggleCommands() | autocmd! RegisterToggleCommands
  augroup END
  function! RegisterToggleCommands() abort
    " collect all commands ending with :...Toggle and re-register them as :Toggle...
    redir => cout
      silent command
    redir END
    let command_list = split(cout, "\n")[1:]   " strip the header line
    let command_list = filter(
                \ map(command_list, { l, v -> Trim((matchlist(v, '\S[A-Z]\+Toggle ') + [''])[0]) }),
                \ '!empty(v:val)')
    for cmd in command_list
      let new_cmd = 'Toggle' . substitute(cmd, 'Toggle$', '', '')
      " register both alias and command (to make tab completion work)
      call CommandAlias(new_cmd, cmd, v:true)
    endfor
  endfunction
endif

" :CloseAllFloatingWindows
" Closes all floating windows, useful for cleaning up messed up pop-ups
if has('nvim-0.4.0')
  command! CloseAllFloatingWindows   lua _G.CloseAllFloatingWindows()
lua << EOF
  _G.CloseAllFloatingWindows = function()
    local closed_windows = {}
    for _, win in ipairs(vim.api.nvim_list_wins()) do
      local config = vim.api.nvim_win_get_config(win)
      if config.relative ~= "" then  -- is_floating_window?
        local bufnr = vim.api.nvim_win_get_buf(win)
        local info = vim.fn.bufname(bufnr)
        if info == "" then info = vim.bo[bufnr].filetype end
        if info ~= "scrollview" then
          vim.api.nvim_win_close(win, false)  -- do not force
          table.insert(closed_windows, ("%s[%d]"):format(info, win))
        end
      end
    end
    if #closed_windows > 0 and vim.o.verbose > 0 then
      print(string.format('Closed %d floating windows: %s',
        #closed_windows, table.concat(closed_windows, ', ')))
    end
  end
EOF
  nnoremap <nowait> <Esc>  <cmd>CloseAllFloatingWindows<CR>
endif

" Open python libraries/modules easily.
" :Pyedit (:pye) -- open the file for the python module
" :Pysplit (:pysp), :Pyvsplit (:pyvs) -- similar, but in :split, :vsplit
" e.g., :Pyedit numpy.core will open $(site-packages)/numpy/core/__init__.py
function! s:setup_pyedit_commands() abort
  if !has('python3') | return | endif
  " This feature requires jedi installed on the host python.
  let g:python_jedi_available = 0
python3 << EOF
try:
  import jedi
  vim.command('let g:python_jedi_available = 1')
except ImportError:
  pass
EOF

  " Get module path for the given module (e.g., numpy.core) *without* actually importing it.
  function! PyModulePath(module) abort
    return py3eval(printf('__import__("jedi").Script("import %s").infer()[0].module_path.__str__()', a:module))
  endfunction

  function! s:edit_python_file(cmd, module) abort
    if !g:python_jedi_available
      echohl WarningMsg | echom 'This feature requires jedi. Please pip install jedi.' | echohl NONE
      return
    end
    try
      exe a:cmd . " " . PyModulePath(a:module)
    catch
      echohl WarningMsg | echom 'Cannot find python module "' . a:module . '"' | echohl NONE
    endtry
  endfunction
  command! -nargs=1 -complete=customlist,CompletePythonModules Pyedit    call s:edit_python_file("edit", <q-args>)
  command! -nargs=1 -complete=customlist,CompletePythonModules Pysplit   call s:edit_python_file("split", <q-args>)
  command! -nargs=1 -complete=customlist,CompletePythonModules Pyvsplit  call s:edit_python_file("vsplit", <q-args>)
  call CommandAlias('pye', 'Pyedit')
  call CommandAlias('pysp', 'Pysplit')
  call CommandAlias('pyvs', 'Pyvsplit')

  " Jedi-based completion for the current python interpreter.
  function! CompletePythonModules(...)
    if !g:python_jedi_available
      return []
    endif

    let l:prefix = get(a:, 1, '')
    let l:cmdline = get(a:, 2, '')
    let l:cmdwords = len(split(l:cmdline, ' '))

    let query = l:prefix
    let completions = py3eval(
          \ printf('[c.full_name for c in __import__("jedi").Script("import %s").complete(line=1)]', query))
    return completions
  endfunction
endfunction
autocmd CmdlineEnter * ++once call s:setup_pyedit_commands()


" }}}
"""""""""""""""""""""""""""""""""""""""""
" 4. Appearance (e.g. Colors, Syntax) {{{
"""""""""""""""""""""""""""""""""""""""""

" color settings
" @see http://www.calmar.ws/vim/256-xterm-24bit-rgb-color-chart.html
set t_Co=256                 " use 256 color
if &background != 'dark'
  set background=dark
endif

if &term =~ '256color'
  " Disable Background Color Erase (BCE) so that color schemes
  " work properly when Vim is used inside tmux and GNU screen.
  set t_ut=
endif

" Use 24-bit color.
if has('termguicolors')
  " Note: We will use GUI colors even if the current session is running under mosh.
  " Although the stable release does not support 24-bit color yet (see GH-961),
  " users are expected to use a HEAD version of mosh both on server and client
  " (e.g., brew install mosh --HEAD,  dotfiles install mosh)
  if !&termguicolors
    set termguicolors
  endif
endif

" apply colorscheme (base: xoria256 + customization)
" See ~/.vim/colors/xoria256-wook.vim
" For neovim + lazy.nvim: see init.lua
if !has('nvim-0.8') && !exists('g:colors_name')
  try
    silent! colorscheme xoria256-wook
  catch /E185/  " Cannot find color scheme
    silent! colorscheme evening
  endtry
endif

" Helper to register Colorscheme autocmd events (and call once immediately)
" so that custom highlight overrides can still be applied after changing colorscheme
function! RegisterHighlights(funcname) abort
  call funcref(a:funcname)()
  augroup Colorscheme_RegisterHighlights
    exec "autocmd Colorscheme *  call " .. a:funcname .. "()"
  augroup END
endfunction
augroup Colorscheme_RegisterHighlights | autocmd! | augroup END

" airline theme: status line and tab line
if has('termguicolors') && &termguicolors
  let g:airline_theme = 'deus'
else
  let g:airline_theme = 'bubblegum'
endif

" show cursorline for active window only
let g:NrHighlight_preserve_filetypes = ['nerdtree', 'neo-tree']
augroup NrHighlight
  autocmd!
  autocmd WinEnter * setlocal cursorline
  autocmd WinLeave * if index(g:NrHighlight_preserve_filetypes, &ft) == -1
        \ |   setlocal nocursorline
        \ | endif
augroup END

" vim syntax: embedded python (P) and lua (l) scripts
" see $VIMRUNTIME/syntax/vim.vim
let g:vimsyn_embed = 'lP'

" filetype detections
au BufRead,BufNewFile /etc/nginx/* if &ft == '' | setfiletype nginx | endif
au BufRead,BufNewFile *.prototxt if &ft == '' | setfiletype yaml | endif
au BufRead,BufNewFile *.ipynb if &ft == '' | setfiletype json | endif

autocmd FileType git setlocal foldlevel=1
autocmd FileType gitcommit setlocal cc=73 textwidth=72

" remove trailing whitespaces on save
let g:enable_strip_trailing_whitespaces = 1
function! StripTrailingWhitespaces()
  if !g:enable_strip_trailing_whitespaces | return | endif
  let l = line('.')
  let c = col('.')
  %s/\s\+$//e
  call cursor(l, c)
endfun
command! StripTrailingWhitespacesToggle  let g:enable_strip_trailing_whitespaces = 1 - g:enable_strip_trailing_whitespaces
      \ | echom 'Auto-strip trailing whitespaces: ' .. (g:enable_strip_trailing_whitespaces ? 'enabled' : 'disabled')

augroup AutoStripTrailingWhitespaces
  autocmd!
  autocmd FileType c,cpp,java,javascript,html,ruby,python,pandoc
        \ autocmd BufWritePre <buffer> :call StripTrailingWhitespaces()
augroup END

" highlight trailing whitespaces
function! HighlightsExtraWhitespace() abort
  highlight ExtraWhitespace ctermbg=red guibg=red
endfunction
call RegisterHighlights('HighlightsExtraWhitespace')

augroup trailing_whitespaces
  autocmd!
  autocmd FileType GV,gitmessengerpopup,gitcommit,fugitive highlight clear ExtraWhitespace
  autocmd BufWinEnter * if _is_file_buffer() | match ExtraWhitespace /\s\+$/ | endif
  autocmd InsertEnter * if _is_file_buffer() | match ExtraWhitespace /\s\+\%#\@<!$/ | endif
  autocmd InsertLeave * if _is_file_buffer() | match ExtraWhitespace /\s\+$/ | endif
  autocmd BufWinLeave * call clearmatches()
augroup END

function! _is_file_buffer() abort
  return &filetype != '' && &buftype == '' && &modifiable
endfunction


" Unset paste when leaving insert mode
if !has('nvim')
  autocmd InsertLeave * silent! set nopaste
endif

" neovim 0.4.0+ : semi-transparent popup menu
if has('nvim') && has('termguicolors') && (&termguicolors)
  silent! set pumblend=10
endif

" Use different popupmenu highlight color for nvim-lsp
if get(g:, 'dotfiles_completion_backend') == '@lsp'
  " oc-yellow-1
  hi Pmenu guibg=#fff3bf
endif

" http://vim.wikia.com/wiki/Identify_the_syntax_highlighting_group_used_at_the_cursor
" neovim 0.9: vim.inspect_pos() or :Inspect
function! ShowSyntaxGroup() abort
  if has('nvim-0.9.0')
    :Inspect
    return
  endif

  silent! TSHighlightCapturesUnderCursor
  echo 'hi<' . synIDattr(synID(line('.'),col('.'),1),'name') . '> trans<'
        \ . synIDattr(synID(line('.'),col('.'),0),'name') . '> lo<'
        \ . synIDattr(synIDtrans(synID(line('.'),col('.'),1)),'name') . '>'
endfunction

noremap <leader>syn  <Cmd>call ShowSyntaxGroup()<cr>

" }}}
"""""""""""""""""""""""""""""""""""""""""
" 5. GUI Options {{{
"""""""""""""""""""""""""""""""""""""""""

" GUI settings
if has('gui_running')
  " GUI apps usually have a default CWD set to $HOME or '/'.
  " Prevent scanning the entire home directory, move to DOTFILES.
  if v:vim_did_enter == 0 && (getcwd() == expand('$HOME') || getcwd() == '/')
    cd $HOME/.dotfiles
  endif

  if has('unix')
    let s:uname = substitute(system('uname -s'), '\n', '', '')
  endif

  if has('gui_win32')
    language mes en         " use english messages (korean characters broken)
    set langmenu=none       " use english context menus (korean characters broken)
    set guioptions-=T       " exclude toolbar
    set guioptions-=m       " exclude menubar

    " font setting for windows
    set guifont=Consolas:h11:cANSI
    set guifontwide=GulimChe:h12:cDEFAULT

  elseif has('gui_gtk2')
    " font setting for Ubuntu linux (GTK)
    set guifont=Ubuntu\ Mono\ derivative\ Powerline\ 12

  elseif has('mac') && exists('g:neovide')
    " neovide
    source $DOTVIM/lua/config/neovide.lua
  endif

endif


" }}}
"""""""""""""""""""""""""""""""""""""""""
" 6. Plugin Settings {{{
"""""""""""""""""""""""""""""""""""""""""

" ----------------------------------------------------------------
" vim-localvimrc {{{

" store and restore all decisions on whether to load lvimrc
let g:localvimrc_persistent = 2

" ---------------------------------------------------------------- }}}
" vim-polyglot {{{

" in favor of TS(treesitter) and vimtex ...
let g:polyglot_disabled = ['python-indent', 'python-compiler', 'latex']

" rust: no needed
let g:polyglot_disabled += ['rust']

" polyglot syntax (coming from vim-lua) conflicts with
" $VIMRUNTIME/syntax/lua.vim shipped with neovim 0.8.0+
" see https://github.com/neovim/neovim/issues/20456
let g:polyglot_disabled += ['lua']

" LSP preview conflicts with polyglot's syntax.
let g:polyglot_disabled += ['markdown']

" ---------------------------------------------------------------- }}}
" vim-startify {{{

let g:startify_bookmarks = [
    \ '~/.vim/vimrc',
    \ '~/.vim/plugins.vim',
    \ ]

let g:startify_skiplist = [
    \ 'COMMIT_EDITMSG',
    \ $VIMRUNTIME .'/doc',
    \ 'plugged/.*/doc',
    \ 'bundle/.*/doc',
    \ ]

" ---------------------------------------------------------------- }}}
" vim-eunuch {{{

" remove all rm-like commands, which are too dangerous (even no confirm...)
let g:PlugConfig['vim-eunuch'] = 's:init_eunuch'
function! s:init_eunuch() abort
  autocmd User LazyInit call s:remove_eunuch_commands()
  function! s:remove_eunuch_commands()
    if !exists('g:loaded_eunuch') | return | endif
    if exists(':Remove') | delcommand Remove | endif
    if exists(':Delete') | delcommand Delete | endif
    if exists(':Unlink') | delcommand Unlink | endif
  endfunction
endfunction

" ---------------------------------------------------------------- }}}
" vim-asterisk (enhanced *) {{{

" Use z (stay) behavior as default
let g:PlugConfig['vim-asterisk'] = 's:init_asterisk'
function! s:init_asterisk() abort
  map *  <Plug>(asterisk-z*)
  map #  <Plug>(asterisk-z#)
  map g* <Plug>(asterisk-gz*)
  map g# <Plug>(asterisk-gz#)
endfunction

" Keep cursor position across matches
let g:asterisk#keeppos = 1

" ---------------------------------------------------------------- }}}
" incsearch {{{

" incsearch.vim
let g:PlugConfig['incsearch.vim'] = 's:init_incsearch'
function! s:init_incsearch() abort
  if !has('patch-8.0.1238')
    map /  <Plug>(incsearch-forward)
    map ?  <Plug>(incsearch-backward)
  endif
  " forward search, but does not move cursor during incsearch
  map g/ <Plug>(incsearch-stay)
endfunction

" incsearch-fuzzy.vim
map z/ <Plug>(incsearch-fuzzy-/)
map z? <Plug>(incsearch-fuzzy-?)
map zg/ <Plug>(incsearch-fuzzy-stay)

" ---------------------------------------------------------------- }}}
" highlightedyank (neovim) {{{

let g:highlightedyank_highlight_duration = 300

if has('nvim')
  " vim-highlightedyank is not used, neovim has built-in support
  " https://neovim.io/doc/user/lua.html#lua-highlight
  augroup HighlightedYank
    autocmd!
    autocmd TextYankPost * silent! lua vim.highlight.on_yank {
          \ higroup = "IncSearch",
          \ timeout = vim.g.highlightedyank_highlight_duration }
  augroup END
endif


" ---------------------------------------------------------------- }}}
" vim-quickhl {{{

nmap <leader>* <Plug>(quickhl-manual-this)
xmap <leader>* <Plug>(quickhl-manual-this)
nmap <leader>8 <Plug>(quickhl-manual-reset)
xmap <leader>8 <Plug>(quickhl-manual-reset)

" ---------------------------------------------------------------- }}}
" vim-highlightedundo {{{

let g:PlugConfig['vim-highlightedundo'] = 's:init_highlightedundo'
function! s:init_highlightedundo() abort
  nmap u     <Plug>(highlightedundo-undo)
  nmap <C-r> <Plug>(highlightedundo-redo)
  nmap U     <Plug>(highlightedundo-Undo)
  nmap g-    <Plug>(highlightedundo-gminus)
  nmap g+    <Plug>(highlightedundo-gplus)

  let g:highlightedundo#highlight_duration_delete = 500
  let g:highlightedundo#highlight_duration_add = 700
endfunction

" ---------------------------------------------------------------- }}}
" which-key.nvim {{{

" Make timeout delay slightly smaller (before popup appears), default is 1000ms
set timeoutlen=500

if has('nvim')
  let g:PlugConfig['which-key.nvim'] = 's:init_which_key'
  function! s:init_which_key() abort
    autocmd User LazyInit call s:setup_which_key()
  endfunction
endif
function! s:setup_which_key()
lua << EOF
  require("which-key").setup {
    window = {
      border = "single",
      winblend = 10,
    },
    layout = {
      height = { min = 4, max = 8 },
    }
  }
EOF
endfunction

" appearances for which-key
function! HighlightsWhichkey() abort
  hi WhichKeyFloat  guibg=#1a2a3a
endfunction
call RegisterHighlights('HighlightsWhichkey')


" ---------------------------------------------------------------- }}}
" vim-commentary {{{

let g:PlugConfig['vim-commentary'] = 's:init_commentary'
function! s:init_commentary() abort
  nmap <leader>c<space>  <Plug>CommentaryLine
  xmap <leader>c<space>  <Plug>Commentary
endfunction


" ---------------------------------------------------------------- }}}
" vim-peekaboo {{{

" Use floating window instead of vsplit for peeking registers.
" https://github.com/junegunn/vim-peekaboo/issues/68
if has('nvim-0.5.0')
  let g:peekaboo_window = "call CreateCenteredFloatingWindow()"
  function! CreateCenteredFloatingWindow()
    let width = float2nr(&columns * 0.8)
    let height = float2nr(&lines * 0.7)
    let top = ((&lines - height) / 2) - 1
    let left = (&columns - width) / 2
    let opts = {
          \ 'relative': 'editor', 'row': top, 'col': left, 'width': width, 'height': height,
          \ 'style': 'minimal', 'border': 'rounded'}

    call nvim_open_win(nvim_create_buf(v:false, v:true), v:true, opts)
    setlocal winblend=10
  endfunction
endif


" ---------------------------------------------------------------- }}}
" editorconfig {{{

let g:EditorConfig_core_mode = 'vim_core'
"let g:EditorConfig_core_mode = 'python_external'
"let g:EditorConfig_core_mode = 'external_command'


" ---------------------------------------------------------------- }}}
" nvim-colorizer {{{
" https://github.com/NvChad/nvim-colorizer.lua#customization

autocmd User LazyInit      call s:setup_nvim_colorizer()
function! s:setup_nvim_colorizer() abort
  if !has('nvim') | return | endif
lua << EOF
  if not pcall(require, 'colorizer') then return end
  require('colorizer').setup {
    user_default_options = {
      RGB = true,
      RRGGBB = true,
      RRGGBBAA = true,
      names = true,
    },
    filetypes = {
      '*';
      css = { css = true, tailwind = true };
      scss = { css = true, tailwind = true };
      sass = { css = true, sass = { enable = true, parsers = {"css"} } };
      less = { css = true, tailwind = true };
      html = { css = true, tailwind = true };
      javascript = { css = true, tailwind = true };
      -- Update color values even if buffer is not focused (for cmp menu, etc.)
      cmp_docs = { always_update = true };
      cmp_menu = { always_update = true };
    },
    buftypes = {
      '*',
      '!prompt';
    }
  }
EOF
endfunction

" ---------------------------------------------------------------- }}}
" Airline {{{
" Note: for airline theme, see the 'appearance' section
" (Neovim 0.5.0+) airline is deprecated in favor of lualine.nvim

" use airline, with powerline-ish theme
let g:airline_powerline_fonts=1

" [section customization] (:h airline-sections)
" See ~/.vim/plugged/vim-airline/autoload/airline/init.vim -> airline#init#sections() to see defaults {{{
" -----------------------

autocmd User AirlineAfterInit call AirlineSectionInit()
function! AirlineSectionInit()
  " define minwidth for some parts
  call airline#parts#define_minwidth('branch', 120)

  " section b: git info (need to call again after define_minwidth/branch)
  let g:airline_section_b = airline#section#create(['hunks', 'branch'])

  " section c:
  let g:airline_section_c = airline#section#create([
        \ '%<', 'file', g:airline_symbols.space, 'readonly',
        \ ])

  " LSP support (this should run after those plugin has been initialized)
  if exists(':LspStatus')  | call airline#parts#define_function('lsp_status', 'AirlineLspStatus') | endif

  " section y: +lsp status, -filetype
  let g:airline_section_x = airline#section#create_right([
        \ 'lsp_status',
        \ 'bookmark', 'tagbar', 'vista', 'gutentags', 'grepper',
        \ ])               " excludes filetype

endfunction

" airline + lsp-status
function! AirlineLspStatus() abort
  return v:lua.LspStatus()
endfunction


" section y (ffenc): skip if utf-8[unix]
let g:airline#parts#ffenc#skip_expected_string = 'utf-8[unix]'

" section z: current position, but more concisely
let g:airline_section_z = 'L%3l:%v'

" }}}

" enable tabline feature
let g:airline#extensions#tabline#enabled = 1

" disable tagbar (in favor of LSP)
let g:airline#extensions#tagbar#enabled = 0

" Display buffers (like tabs) in the tabline
" if there is only one tab
let g:airline#extensions#tabline#show_buffers = 1

" suppress mixed-indent warning for javadoc-like comments (/** */)
let g:airline#extensions#whitespace#mixed_indent_algo = 1

" Disable neomake
let g:airline#extensions#neomake#enabled = 0

" ---------------------------------------------------------------- }}}
" nvim-bqf (enhanced quickfix) {{{
" See ~/.dotfiles/nvim/lua/config/quickfix.lua

" ---------------------------------------------------------------- }}}
" asyncrun.vim (Deprecated in favor of asyncrun.nvim) {{{
" see ~/.vim/after/syntax/qf.vim for color + highlights
" see $DOTVIM/lua/config/build.lua

" Note that asyncrun.vim allows only one job running at a time.
" For multiple job dispatch, we may need more advanced plugins.

" (Settings)
" Do not open quickfix after job starts
let g:asyncrun_open = 0
" Trigger autocmd QuickFixCmdPost make after job finish (triggers errormarker)
let g:asyncrun_auto = "make"


command! -nargs=* -complete=customlist,MakeCommandCompletion AsyncMake
      \ call AsyncMake(<q-args>)
function! AsyncMake(qarg) abort
  let l:qarg = (a:qarg)
  if &makeprg == 'make'
    exe 'AsyncRun make ' . l:qarg
  else
    if !empty(l:qarg)
      echohl WarningMsg |
      echo 'Error: Cannot have arguments with &makeprg = ' . &makeprg
      echohl None
      return
    endif
    exe 'AsyncRun ' . ExpandCmd(&makeprg)
  endif
endfunction
call CommandAlias('R', 'AsyncRun')


" Alias :Run => :AsyncRun
call CommandAlias('Run', 'AsyncRun', v:true)

" legacy asyncrun.vim autocmd events: notification callback when job finishes
augroup AsyncRunEvents
  autocmd!
  autocmd User AsyncRunStart  call OnAsyncRunJobStart(v:null)
  autocmd User AsyncRunStop   call OnAsyncRunJobFinished(v:null)
augroup END

let g:asyncrun_job_status = {}
function! OnAsyncRunJobStart(job) abort
  if !empty(a:job)
    let l:cmdline = a:job.cmdline
  else
    let l:cmdline = g:asyncrun_info   " legacy asyncrun.vim
  endif

  let l:jobid = 3000   " asyncrun.vim supports only ONE concurrent job
  let g:asyncrun_job_status[l:jobid] = {
        \ 'status': 'running',
        \ 'cmdline': l:cmdline,
        \ }
endfunction

function! OnAsyncRunJobFinished(job) abort
  let l:jobid = 3000   " asyncrun supports only ONE concurrent job
  " a:job => nil for legacy asyncrun.vim, an object for the new asyncrun.nvim (currently not used)

  let l:job = get(g:asyncrun_job_status, l:jobid, {})
  if l:job == {} | return | endif
  let l:job['status'] = g:asyncrun_status

  " Notifications.
  if l:job['status'] != "success"
    let l:failure_message = l:job['cmdline']
    let l:job_title = printf("AsyncRun")   " TODO: How to get the cmd or job info?
    call VimNotify('Job Failed: ' . l:failure_message, 'warn', {'title': l:job_title})

    " Automatically show quickfix window if the job has failed.
    " open the qf window at the bottom and scroll all the way down
    Copen | cbottom
  endif

  " Statusline integration
  " Remove the job from the statusline after 1 seconds
  if !exists('*timer_start')
    silent! unlet g:asyncrun_job_status[l:jobid]
  else
    call timer_start(1000, { -> execute(printf(
          \"silent! unlet g:asyncrun_job_status[%d]", l:jobid)) })
  endif
endfunction

" Actions in the asyncrun window
augroup AsyncRunQuickfixWindow
  autocmd!
  " Ctrl-C twice: kill the asyncrun job
  autocmd FileType qf    noremap <silent> <nowait> <buffer> <C-C>        <cmd>echo 'Press Ctrl-C twice to kill the job'<CR>
  autocmd FileType qf    noremap <silent> <nowait> <buffer> <C-C><C-C>   <cmd>call AsyncrunQuickfixCtrlC()<CR>
augroup END
function! AsyncrunQuickfixCtrlC() abort
  " legacy asyncrun.vim
  if get(g:, "asyncrun_status") == "running"
    AsyncStop
  endif
endfunction

" }}}


" ---------------------------------------------------------------- }}}
" errormarker.vim (quickfix to signs) {{{

" signs
let g:errormarker_errortext = '✘'
let g:errormarker_warningtext = ''
let g:errormarker_errortextgroup = "DiagnosticSignError"
let g:errormarker_warningtextgroup = "DiagnosticSignWarn"

" text line
let g:errormarker_errorgroup = 'ErrormarkerErrorText'
let g:errormarker_warninggroup = 'ErrormarkerWarningText'

function! HighlightsErrormarker() abort
  hi ErrormarkerErrorText       guibg=#3d0f0f
  hi ErrormarkerWarningText     guibg=NONE
endfunction
call RegisterHighlights('HighlightsErrormarker')

" ---------------------------------------------------------------- }}}
" FZF {{{

" If floating window is not available, use 33%-bottom layout
let g:fzf_layout = { 'down' : '~33%' }

" vim 8.2+: popup window (junegunn/fzf.vim#821)
if !has('nvim') && has('patch-8.2.194')
  let g:fzf_layout = { 'window' : { 'width': 0.9, 'height': 0.6 } }
  call extend(g:fzf_layout['window'], { 'border': 'sharp', 'highlight': 'FZFFloatBorder' })
  hi def link FZFFloatBorder Comment
endif

" neovim 0.4+: FZF + floating window.
if has('nvim-0.4')
  " On floating windows, we use reverse layout, i.e. prompt is at the above.
  let $FZF_DEFAULT_OPTS = $FZF_DEFAULT_OPTS . ' --layout=reverse  --margin=1,2'
  let g:fzf_layout = { 'window': 'call FloatingFZF()' }

  function! FloatingFZF()
    let buf = nvim_create_buf(v:false, v:true)
    call setbufvar(buf, '&signcolumn', 'no')

    let height = float2nr(0.6 * &lines)
    let width = float2nr(0.9 * &columns)
    let horizontal = float2nr((&columns - width) / 2)
    let vertical = (&lines - height) / 2

    let opts = {
          \ 'relative': 'editor',
          \ 'row': vertical,
          \ 'col': horizontal,
          \ 'width': width,
          \ 'height': height,
          \ }
    let winhl = 'Normal:FZFFloatNormal'
    if has('nvim-0.5')
      let opts['border'] = 'rounded'
      let winhl = winhl . ',FloatBorder:FZFFloatNormal'
    end
    let fwinnr = nvim_open_win(buf, v:true, opts)
    call setwinvar(fwinnr, '&winhl', winhl)

    " Additional keymappings on the floating terminal buffer for fzf
    " e.g. make <C-W>{H, J, K, L} work as if it were normal vim windows
    tnoremap <buffer> <silent> <C-w>H       <C-\><C-n>:wincmd H<CR>:startinsert<CR>
    tnoremap <buffer> <silent> <C-w>J       <C-\><C-n>:wincmd J<CR>:20wincmd _<CR>:startinsert<CR>
    tnoremap <buffer> <silent> <C-w>K       <C-\><C-n>:wincmd K<CR>:20wincmd _<CR>:startinsert<CR>
    tnoremap <buffer> <silent> <C-w>L       <C-\><C-n>:wincmd L<CR>:startinsert<CR>
    " remap wincmd commands so that the window can work without the <C-\><C-n> prefix
    tnoremap <buffer> <silent> <C-l>        <C-\><C-n>:wincmd l<CR>
    tnoremap <buffer> <silent> <C-w>h       <C-\><C-n>:wincmd h<CR>
    tnoremap <buffer> <silent> <C-w>j       <C-\><C-n>:wincmd j<CR>
    tnoremap <buffer> <silent> <C-w>k       <C-\><C-n>:wincmd k<CR>
    tnoremap <buffer> <silent> <C-w>l       <C-\><C-n>:wincmd l<CR>
    tnoremap <buffer> <silent> <C-w>w       <C-\><C-n>:wincmd w<CR>
    " Ctrl-{J, K} scrolls fzf (rather than wincmd)
    tnoremap <buffer> <silent> <C-j>        <Down>
    tnoremap <buffer> <silent> <C-k>        <Up>

  endfunction

  function! HighlightsFZF() abort
    hi FZFFloatNormal term=None guibg=#1a2a31
  endfunction
  call RegisterHighlights('HighlightsFZF')
endif

" Inside vim, set environment variable FZF_DEFAULT_COMMAND
" so that it can list the files by 'git ls-files' or 'ag'.
if executable('ag')
  "let $FZF_DEFAULT_COMMAND = '(git ls-files ":/" || ag -l -g "") | LC_COLLATE=C sort | uniq  2> /dev/null'
  let $FZF_DEFAULT_COMMAND = 'ag -l -g "" 2> /dev/null'
endif

" fzf.vim options

" neovim: fzf.vim is deprecated in favor of fzf-lua.
" During migration, all fzf-vim commands will be prefixed with Fzf
" e.g., History => FzfHistory
let g:fzf_vim = {}
if has('nvim')
  let g:fzf_vim.command_prefix = 'Fzf'
endif

" :Buffers => Jump to the existing window if possible
let g:fzf_vim.buffers_jump = 1

" }}}

function! s:get_git_workdir(path)
  let curr = fnamemodify(expand(a:path), ':p')
  if empty(curr)   " empty buffer, etc.: fall back to current dir
    let curr = getcwd()
  endif
  return FugitiveWorkTree(FugitiveExtractGitDir(curr))
endfunction

" Command :F (FzfSmart)
function! FzfSmart(path) abort
  doautocmd CmdlineEnter  " for lazy-loaded plugins

  " If neovim, try to load fzf-lua early because this can be executed much before
  let l:fzf_lua = has('nvim') && luaeval('pcall(require, "fzf-lua")')

  " If args are given (e.g. :F <args>), resolve it to a directory
  let l:path = Trim(a:path)
  if !empty(l:path)
    let l:path = expand(l:path)
    if !isdirectory(l:path)   " for a file, the directory that contains it
      if filereadable(l:path)
        let l:path = fnamemodify(l:path, ":h")
      else  " does not exist, error
        echohl WarningMsg | call Echom('Path does not exist: ' . l:path) | echohl NONE | return 0
      endif
    endif
  endif

  " (1) If a FZF tree/explorer is shown with no argument,
  " invoke :Files (with preview) rather than :GFiles
  if empty(l:path) && &filetype == 'nerdtree'
    let l:target_path = b:NERDTree.root.path._str()
    " if the current buffer is the pinned on in the tab (vim-nerdtree-tabs),
    " let fzf open files in another window. Otherwise, open in the current window.
    if exists('t:NERDTreeBufName') && bufname('%') == t:NERDTreeBufName
      wincmd w   " we need to move the focus outside nerdtree
    endif
    execute ':Files ' . l:target_path
    return 1
  endif
  if empty(l:path) && &filetype == 'neo-tree'
    let l:target_path = luaeval('require("config.neotree").get_path()')
    if !empty(l:target_path)
      execute ':Files ' . l:target_path
      return 1
    endif
  endif

  " (2) If the given path (or CWD) is in a git repo,
  " invoke :GFiles (plus untracked files)
  let l:git_workdir = s:get_git_workdir(!empty(l:path) ? l:path : '%')
  if ! empty(l:git_workdir)
    let l:old_cwd = getcwd()
    execute ':tcd ' . l:git_workdir
    try
      echon ':GitFiles! (' | echohl Directory | echon getcwd() | echohl NONE | echon ')' | echon '    '
      if l:fzf_lua  " see $DOTVIM/lua/config/fzf.lua
        exec 'GitFiles! ' . l:path
      else  " vanilla vim, using fzf.vim
        exec 'GFiles --cached --others --modified --exclude-standard --deduplicate ' . l:path
      endif
    finally
      execute ':tcd ' . l:old_cwd
    endtry
    return 2

  " (3) not in git repo, invoke :Files <args> by fallback
  else
    execute ':Files ' . l:path
    return 3
  endif
endfunction

" :F is a shortcut for :GFiles or :FZF
command! -complete=dir -nargs=* F        call FzfSmart(<q-args>)
call CommandAlias('FC', 'F ~/.dotfiles/', v:true)
call CommandAlias('FP', 'F ~/.dotfiles/vim/plugged/<C-R>=EatWhitespace()<CR>', v:true)
call CommandAlias('FPlug', 'F ~/.dotfiles/vim/plugged/<C-R>=EatWhitespace()<CR>', v:true)
call CommandAlias('Fplug', 'F ~/.dotfiles/vim/plugged/<C-R>=EatWhitespace()<CR>', v:false)
call CommandAlias('Flua', expand("F $VIMRUNTIME/lua"), v:true)

" Invoke F (FZF) Using Ctrl-P
nmap <C-P> <cmd>F<CR>


" custom commands using fzf
" -------------------------

" Utility functions brought from @junegunn/fzf.vim {{{
" Copyright (c) Junegunn Choi, under MIT License
function! s:get_color(attr, ...)
  let gui = has('termguicolors') && &termguicolors
  let fam = gui ? 'gui' : 'cterm'
  let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
  for group in a:000
    let code = synIDattr(synIDtrans(hlID(group)), a:attr, fam)
    if code =~? pat
      return code
    endif
  endfor
  return ''
endfunction

let s:ansi = {'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34, 'magenta': 35, 'cyan': 36}

function! s:csi(color, fg)
  let prefix = a:fg ? '38;' : '48;'
  if a:color[0] == '#'
    return prefix.'2;'.join(map([a:color[1:2], a:color[3:4], a:color[5:6]], 'str2nr(v:val, 16)'), ';')
  endif
  return prefix.'5;'.a:color
endfunction

function! s:ansi(str, group, default, ...)
  let fg = s:get_color('fg', a:group)
  let bg = s:get_color('bg', a:group)
  let color = (empty(fg) ? s:ansi[a:default] : s:csi(fg, 1)) .
        \ (empty(bg) ? '' : ';'.s:csi(bg, 0))
  return printf("\x1b[%s%sm%s\x1b[m", color, a:0 ? ';1' : '', a:str)
endfunction

for s:color_name in keys(s:ansi)
  execute "function! s:".s:color_name."(str, ...)\n"
        \ "  return s:ansi(a:str, get(a:, 1, ''), '".s:color_name."')\n"
        \ "endfunction"
endfor
" }}}


" Python helpers
let g:python_package_alias = {
      \ 'np': 'numpy', 'pd': 'pandas', 'mpl': 'matplotlib', 'plt': 'matplotlib', 'sns': 'seaborn',
      \ 'tf': 'tensorflow', 'tfa': 'tensorflow_addon', 'tfp': 'tensorflow_probability',
      \ 'tfio': 'tensorflow_io', 'snt': 'sonnet', 'ta': 'tf_agents',
      \ }

function! PythonSitePackagesDir()
  return systemlist('python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"')[0]
endfunction

" Command line completion for python modules in site-packages.
function! CompletePythonSitePackages(...)
  let l:prefix = get(a:, 1, '')
  let l:cmdline = get(a:, 2, '')
  let l:cmdwords = len(split(l:cmdline, ' '))
  if l:cmdwords >= 3 || (l:cmdwords == 2 && l:cmdline =~ ' $')
    return []  " from the second argument, no completion
  endif
  if empty(get(g:, 'python_site_packages_cache', []))
    " Listing of the site-packages directory (cached)
    autocmd CmdlineLeave * ++once  let g:python_site_packages_cache = []
    let g:python_site_packages_cache = systemlist(
          \ 'python -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())" ' .
          \ ' | xargs -I{} find {}/ -maxdepth 1 -type d ' .
          \ ' | grep -v "\(dist\|egg\)-info$" | grep -v "\.egg$" | grep -v "__pycache__" ' .
          \ ' | sort ' .
          \ ' | sed -e "s#\(.*\)/\(.*\)#\2#"'
          \)
  endif
  let filter_expr = '!empty(v:val)'
  if !empty(l:prefix)
    let filter_expr .= printf(' && v:val =~# "^%s"', escape(l:prefix, "'\""))
  endif
  return filter(copy(g:python_site_packages_cache), filter_expr)
endfunction


" FZF-based finder commands
" -------------------------

" :Z -- cd to recent working directories using fasd
command! -nargs=* Z call s:Z(<q-args>)
function! s:Z(args) abort
  call fzf#run(fzf#wrap({
      \ 'source':  printf('fasd -Rdl "%s"',
      \                   escape(empty(a:args) ? '' : a:args, '"\'))
      \           . ' | ' . s:sed_highlight_path_last_segment,
      \ 'options': ['--ansi', '-1', '-0', '--no-sort', '--no-multi',
      \             '--prompt', 'Z> ',
      \             ] + s:fzf_preview_directory_if_applicable(),
      \ 'sink':    function('s:Z_sink'),
      \}))
endfunction
function! s:Z_sink(path) abort
  exec 'tcd ' . a:path
  Neotree
endfunction

" :Plugs -- list all vim plugins and open the directory of the selected
if !empty(g:plugs)
  command! -nargs=* Plugs call fzf#run
      \(fzf#wrap({
      \ 'source':  map(sort(keys(g:plugs)), 'g:plug_home . "/" . s:yellow(v:val)'),
      \ 'options': ['--ansi', '--delimiter', '/', '--nth', '-1', '--no-multi', '--inline-info',
      \             '--query', <q-args>, '--prompt', 'Plugs> ',
      \             ] + s:fzf_preview_directory_if_applicable(),
      \ 'sink':    function('s:Z_sink'),
      \}))
endif

" :SitePackages -- quickly jump to the site-packages directory for the current python
command! -nargs=* SitePackages call s:SitePackages(<q-args>)
function! s:SitePackages(query) abort
  let l:root = Trim(system('python -W ignore -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())" 2>/dev/null'))
  call fzf#run(fzf#wrap({
      \ 'source':  ('find . -mindepth 1 -maxdepth 1 -type d ' .
      \             ' | grep -v "\(dist\|egg\)-info$" | grep -v "\.egg$" | sort ' .
      \             ' | ' . s:sed_highlight_path_last_segment
      \ ),
      \ 'options': ['--ansi', '--delimiter', '/', '--nth', '-1', '--no-multi', '--inline-info',
      \             '--query', a:query, '--prompt', 'SitePackages> ',
      \             ] + s:fzf_preview_directory_if_applicable() +
      \             ['--header', l:root],
      \ 'dir':     l:root,
      \ 'sink':    'Neotree'
      \}))
endfunction

function! s:fzf_preview_directory_if_applicable()
  let l:fzf_options = []
  if &columns >= 100 && executable("tree")
    " enable directory preview if width is sufficient; max depth 2.
    let l:fzf_options += [
          \ '--preview-window', 'right:30%',
          \ '--preview', 'tree -C -I "node_modules|.git|*.pyc" -L 2 -x {}',
          \ '--bind', '?:toggle-preview,ctrl-/:toggle-preview',
          \ '--header', ':: Press "?" or "CTRL-/" to toggle preview'
          \ ]
  endif
  return l:fzf_options
endfunction

let s:sed_highlight_path_last_segment = 'sed -e "s#\(.*\)/\(.*\)#\1/$(tput setaf 3)\2$(tput sgr0)#"'


" ---------------------------------------------------------------- }}}
" vim-floaterm {{{

let g:floaterm_position = 'center'
let g:floaterm_background = '#1a2a31'

function! HighlightsFloaterm() abort
  hi Floaterm         guibg=#1a2a31
  hi FloatermBorder   guibg=#1a2a31 guifg=white
  hi def link FloatermNF        Floaterm
  hi def link FloatermNC        Floaterm
  hi def link FloatermBorderNF  FloatermBorder
endfunction
call RegisterHighlights('HighlightsFloaterm')

command! -bang -nargs=? -complete=shellcmd  T                    :FloatermToggleOrNew<bang> <args>
command! -bang -nargs=? -complete=shellcmd  FT                   :FloatermToggleOrNew<bang> <args>
command! -bang -nargs=? -complete=shellcmd  FloatermToggleOrNew  call FloatermToggleOrNew(<q-args>, <bang>0)

function! FloatermToggleOrNew(...) abort
  " FloatermToggleOrNew(cmdline: str, bang: int = 0)
  "  - if bang=1, run the command in a shell use sendkeys.
  "  - if bang=0, execute the program without shell
  " We use a dedicated name 'FT' for the floaterm triggered by the :FT, :T command
  let ft_bufnr = floaterm#terminal#get_bufnr('FT')
  let bang = a:0 >= 2 ? a:2 : 0
  let cmd = a:0 > 0 ? Trim(a:1) : ''
  if ft_bufnr == -1
    if empty(cmd)
      " when there is no existing floaterm window
      " and no argument given, just open a new floaterm shell.
      execute ':FloatermNew --name=FT --autoclose=1'
    elseif !bang
      " if argument given but without bang,
      " use it as the command line to execute (e.g. :FT git status)
      execute ':FloatermNew --name=FT --autoclose=0 ' . cmd
    elseif bang
      " Given bang, (e.g. :FT! git status) run cmd in a (interactive) shell.
      execute ':FloatermNew --name=FT --autoclose=1'
      let ft_bufnr = floaterm#terminal#get_bufnr('FT')
      call floaterm#terminal#send(ft_bufnr, [cmd])
    endif
  else
    FloatermToggle FT
  endif
endfunction

" Use 90% width for floaterm. If error occurs, update the plugin
let g:floaterm_width = 0.9
let g:floaterm_height = 0.85

" Turn off auto-hiding of previous floaterm windows,
" in favor of the autohide autocmd below (only works for 'floating' windows)
let g:floaterm_autohide = 0

" Turn off GIT_EDITOR integration.
let g:floaterm_giteditor = 0

augroup FloatermCustom
  autocmd!
  autocmd user FloatermOpen   call s:floaterm_configure_after()
augroup END
function! s:floaterm_configure_after()
  " Automatically hide floaterm when leaving the buffer:
  " When leaving out of floaterm, hide it so that it doesn't cover other windows
  " This autocmd should be set after 'opening' floaterm is complete (i.e., autocmd FloatermOpen),
  " because BufLeave can be triggered before that (e.g., while creating border windows).
  augroup FloatermAutoHide
    autocmd!
    autocmd BufLeave <buffer> call <SID>autohide_floaterm()
  augroup end
  " Use <C-z> to auto-hide the current floaterm window and move back to the previous window
  tnoremap <buffer> <silent> <C-z>    <C-\><C-n>:wincmd p<CR>
endfunction
function! s:floaterm_hide(bufnr)
  "return floaterm#window#hide_floaterm(a:bufnr)   " API removed (d7196ee0)
  execute a:bufnr . 'FloatermHide'
endfunction
function! s:autohide_floaterm() abort
  " Ensure this BufLeave event handler gets executed only once
  autocmd! FloatermAutoHide
  if &filetype == 'floaterm' && nvim_win_get_config(0).relative != ""
    let l:FuncHide = function('s:floaterm_hide', [bufnr('%')])   " neovim or 7.4.1836+
    call timer_start(0, { -> l:FuncHide() })
  endif
endfunction


" ---------------------------------------------------------------- }}}
" wilder.nvim - Better wildmenu {{{
" https://github.com/gelguy/wilder.nvim#example-configs

set wildchar=<Tab>
set wildcharm=<Tab>

if has('nvim')
  augroup WilderLazyInit
    autocmd!
    autocmd CmdlineEnter * ++once call s:check_wilder_rplugins()
    autocmd CmdlineEnter * ++once call s:setup_wilder()
  augroup END
endif
function! s:setup_wilder() abort
  " If python3 host has failed to load, do not activate wilder.
  if !luaeval('require("config.pynvim")()') | return | endif

  " only / and ? are enabled by default
  try
    call wilder#setup({'modes': ['/', '?', ':']})
  catch /E117/  " Do nothing when wilder.nvim is not installed.
    return
  endtry

  cmap <expr> <Tab>   wilder#in_context() ? wilder#next()     : "\<Tab>"
  cmap <expr> <S-Tab> wilder#in_context() ? wilder#previous() : "\<S-Tab>"

  " pipeline: enable fuzzy command matching, fuzzy file search (GH-101), search history
  let l:pipeline = {}
  let l:pipeline.noop = [{_ -> v:false}]
  let l:pipeline.cmdline = wilder#cmdline_pipeline({
        \ 'fuzzy' : 2, 'fuzzy_filter': wilder#lua_fzy_filter() })
  let l:pipeline.history = [ wilder#check({_, x -> empty(x)}), wilder#history() ]

  let l:pipeline.file_fuzzy = executable('fd') && has('nvim-0.5.0') ?
        \ wilder#python_file_finder_pipeline({
        \   'debounce': 50,
        \   'file_command': {ctx, arg -> v:lua._wilder_fuzzy_fd_pipeline_command(ctx, arg, 'f')},
        \   'dir_command':  {ctx, arg -> v:lua._wilder_fuzzy_fd_pipeline_command(ctx, arg, 'd')},
        \   'filters': ['fuzzy_filter', 'difflib_sorter'],
        \   'path': {-> getcwd()},
        \ }) : l:pipeline.noop

lua << EOF
_G._wilder_fuzzy_fd_pipeline_command = function(ctx, arg, file_mode)
  local cmd = {'fd'}

  -- Do not enable if the argument starts with "./",
  if string.sub(arg, 1, 2) == "./" then return false end
  -- Do not enable if it's not a git repository, for the performance reason
  local is_git_repo = (vim.fn.FugitiveExtractGitDir(vim.fn.getcwd()) ~= '')
  if not is_git_repo then return false end

  if file_mode == 'f' then   -- both file and directory (TODO: sharkdp/fd#436)
    cmd = vim.list_extend(cmd, {'--type', 'f', '--type', 'd'})
  else  -- directory only
    cmd = vim.list_extend(cmd, {'--type', 'd'})
  end

  local arg_basedir, arg_filename = string.match(arg, "(.-)([^\\/]-%.?[^%.\\/]*)$")
  -- Starting with dot: include hidden files, but restrict to the depth 1 only (exact subdir)
  if string.sub(arg_filename, 1, 1) == "." then
    cmd = vim.list_extend(cmd, {'--no-ignore', '--hidden', '--maxdepth', '1'})
  end

  table.insert(cmd, '.')
  -- When the current path argument exactly matches a subdirectory, search inside it (narrow down);
  -- otherwise search the entire current directory
  if not (arg_basedir == "") and vim.fn.isdirectory(arg_basedir) == 1 then
    table.insert(cmd, './' .. arg_basedir)
  end

  -- Avoid accidentally scanning the large volume ...
  if not vim.tbl_contains(cmd, '--maxdepth') then
    cmd = vim.list_extend(cmd, {'--maxdepth', '5'})
  end

  -- vim.o.titlestring = table.concat(cmd, ' ')   -- just for debugging :)
  return cmd
end
EOF

  call wilder#set_option('pipeline', [
        \ wilder#branch(
        \   l:pipeline.file_fuzzy,
        \   l:pipeline.history,
        \   l:pipeline.cmdline,
        \   wilder#vim_search_pipeline(),
        \ )])

  " renderer: use popupmenu (may be different than Pmenu, some yellow-ish color)
  let l:wilder_hl = {
        \ 'default': wilder#make_hl('WilderPmenu', [
        \     {}, {'foreground': 0, 'background': 11},
        \     {'foreground': 'black', 'background': '#ffec99'}
        \ ]),
        \ 'accent': wilder#make_hl('WilderAccent', 'WilderPmenu', [
        \     {}, {}, {'foreground': '#f03e3e', 'bold': 1, 'underline': 1}])
        \ }
  let l:wilder_renderer_option = {
        \ 'apply_incsearch_fix': 1,
        \ 'winblend': &pumblend,
        \ 'highlighter': wilder#lua_fzy_highlighter(),
        \ 'highlights': l:wilder_hl,
        \ 'reverse': v:false,
        \ 'max_height': '50%',
        \ 'max_width': '90%',
        \ 'left': [' ', wilder#popupmenu_devicons()],
        \ 'right': [' ', wilder#popupmenu_scrollbar()],
        \ }
  call wilder#set_option('renderer', wilder#popupmenu_renderer(l:wilder_renderer_option))

  " Additional custom keymap for wilder
  " <C-space> in the command line: refresh completion (close and start again)
  cmap <expr> <C-space> [wilder#main#stop(), wilder#main#start_from_normal_mode()][-1]

  call wilder#main#start()
endfunction

function! s:check_wilder_rplugins() abort
  if !exists(':UpdateRemotePlugins') | return | endif  " noplugin?

  " Note that rplugin functions are defined only after its first invocation,
  " so we first force loading it even before wilder initialization is complete.
  doautocmd FuncUndefined _wilder_init
  if !exists('*_wilder_init') && luaeval('require("config.pynvim")()')  " has('python3')
    UpdateRemotePlugins
    let l:msg = ':UpdateRemotePlugins has been executed. Please restart neovim.'
    echohl WarningMsg | echom l:msg | echohl None
    call VimNotify(l:msg, 'error', {'timeout': 30 * 1000, 'title': 'wilder'})
  endif
endfunction

" ---------------------------------------------------------------- }}}
" Dash {{{

" ----------------------------------------------------------------------------
" <Leader>/ : Launch Dash on the words on cursor or in block
" ----------------------------------------------------------------------------
nnoremap <leader>/ :Dash <cword><cr>
xnoremap <leader>/ "gy:Dash <c-r>g<cr>gv

" ---------------------------------------------------------------- }}}
" SuperTab {{{

" Use 'omnicomplete' as the default completion type.
" It may fallback to default keyword completion (<C-P>).
let g:SuperTabDefaultCompletionType = '<C-X><C-O>'

" sometimes we may want to insert tabs or spaces for indentation.
" no tab completion at the start of line or after whitespace.
let g:SuperTabNoCompleteAfter = ['^', '\s']


" ---------------------------------------------------------------- }}}
" NerdTree (deprecated) {{{

" change CWD when the NERDtree is first loaded to the directory initialized in
" (e.g. change CWD to the directory hitted by CtrlPZ)
let g:NERDTreeChDirMode = 1

" <Leader>N toggles NERDTree (across tab)
map <Leader>N <plug>NERDTreeTabsToggle<CR>

" By default nerdtree maps <C-j> and <C-k> which I want to reserve for window
" navigation instead for the sake consistency. Therefore use different keymaps.
let g:NERDTreeMapJumpNextSibling = '<leader><C-j>'
let g:NERDTreeMapJumpPrevSibling = '<leader><C-k>'

" Startup Options (do NOT show automatically)
let g:nerdtree_tabs_open_on_console_startup = 0
let g:nerdtree_tabs_open_on_gui_startup = 0

" filter out some files, by extension
let NERDTreeIgnore = [
      \ '\.git$', '^__node_modules__$',
      \ '\.pyc$', '^__pycache__$', '^.pytest_cache$', '.mypy_cache', '\.egg-info$',
      \ '\.class$', '\.o$',
      \]
let NERDTreeRespectWildIgnore = 1   " see wildignore

" ---------------------------------------------------------------- }}}
" neotree {{{

" see nvim/lua/plugins/ui.lua
" see nvim/lua/config/neotree.lua

if !exists(':Neotree')  " fallback to NERDTree for vanilla vim
  command! -complete=dir -nargs=* Neotree  NERDTree
end

" ---------------------------------------------------------------- }}}
" nvim-tree.lua {{{
" DEPRECATED: Use neo-tree.nvim instead.

function! s:setup_nvimtree() abort
  " Ignore some files
  let g:nvim_tree_ignore = ['.git', '__node_modules__',
        \ '__pycache__', '.pytest_cache', '.mypy_cache', '.cache',
        \ ]
  let g:nvim_tree_gitignore = 1
  " Use trailing '/' for folders
  let g:nvim_tree_add_trailing = 1
  " Show indent markers (vertical segments)
  let g:nvim_tree_indent_markers = 1
  " Don't ask which window should be picked to open files.
  let g:nvim_tree_disable_window_picker = 1

lua << EOF
  -- Config: https://github.com/kyazdani42/nvim-tree.lua#setup
  if not pcall(require, 'nvim-tree') then return end
  local tree_cb = require('nvim-tree.config').nvim_tree_callback
  require('nvim-tree').setup {
    -- Update the location cursor whenever entering a buffer
    update_focused_file = {
      enable = true,
    },
    -- Custom Keymappings
    view = {
      mappings = {
        list = {
          { key = "?",                            cb = tree_cb("toggle_help") },
          { key = "r",                            cb = tree_cb("refresh") },
        }
      }
    }
  }
EOF
endfunction

let g:PlugConfig['nvim-tree.lua'] = 's:init_nvim_tree'
function! s:init_nvim_tree() abort
  autocmd User LazyInit      call s:setup_nvimtree()
  nmap <leader>E  :NvimTreeToggle<CR>

  " On focus, refresh automatically
  augroup NvimTreeCustomization
    autocmd!
    autocmd BufEnter NvimTree*   :NvimTreeRefresh
  augroup end
endfunction

" ---------------------------------------------------------------- }}}
" Voom {{{

let g:voom_ft_modes = {'pandoc': 'markdown', 'tex': 'latex'}

"nnoremap <leader>V :VoomToggle<CR>

" ---------------------------------------------------------------- }}}
" Easymotion {{{

" Trigger <,f> to launch easymotion global jump
nmap <leader>f <Plug>(easymotion-s)

" backward, forward search may mapped to easymotion.
"map  / <Plug>(easymotion-sn)
"omap / <Plug>(easymotion-tn)

" Jump to first match, by Enter or Space
let g:EasyMotion_enter_jump_first = 1
let g:EasyMotion_space_jump_first = 1

" ---------------------------------------------------------------- }}}
" quick-scope {{{

" Trigger a highlight in the appropriate direction when pressing these keys:
let g:qs_highlight_on_keys = ['f', 'F', 't', 'T']

function! HighlightsQuickscope() abort
  highlight QuickScopePrimary   guifg=#afff5f gui=underline ctermfg=155 cterm=underline
  highlight QuickScopeSecondary guifg=#5fffff gui=underline ctermfg=81  cterm=underline
endfunction
call RegisterHighlights('HighlightsQuickscope')

" ---------------------------------------------------------------- }}}
" vim-easy-align {{{

" Start interactive EasyAlign in visual mode (e.g. vipga)
xmap ga <Plug>(EasyAlign)
" Start interactive EasyAlign for a motion/text object (e.g. gaip)
nmap ga <Plug>(EasyAlign)

" ---------------------------------------------------------------- }}}
" indent-blankline.nvim {{{
" :help indent-blankline

let g:PlugConfig['indent-blankline.nvim'] = 's:init_indent_blankline'
function! s:init_indent_blankline() abort
  let g:indent_blankline_char = '┊'
  let g:indent_blankline_filetype_exclude = ['help', 'NvimTree', 'pandoc']
  let g:indent_blankline_buftype_exclude = ['terminal']
  let g:indent_blankline_show_first_indent_level = v:false
endfunction

function! HighlightsIndentBlankline() abort
  highlight! IndentBlanklineChar guifg=#444444 gui=nocombine
endfunction
call RegisterHighlights('HighlightsIndentBlankline')

" ---------------------------------------------------------------- }}}
" indentLine {{{
" (not used in neovim)

let g:PlugConfig['indentLine'] = 's:init_indentline_legacy'
function! s:init_indentline_legacy() abort

  " conceal might break latex/json/markdown syntax, etc.
  " see also after/ftplugin/*.vim settings for conceal level configuration.
  let g:indentLine_conceallevel = 1

  " conceal values might not work well for tex files (hides some characters)
  let g:indentLine_fileTypeExclude = ['tex', 'markdown', 'pandoc', 'NvimTree', 'floaterm', 'tagbar']

  " command alias
  command! -nargs=0 ToggleIndentLines   :IndentLinesToggle

endfunction

" ---------------------------------------------------------------- }}}
" nvim-scrollview {{{

" Make the scrollbar aware of foldings (see dstein64/nvim-scrollview#11).
let g:scrollview_mode = 'virtual'

" Do not use signs, it looks a bit messy without proper configs (see dstein64/nvim-scrollview#85).
let g:scrollview_signs_on_startup = ['']

" Disable scrollbar on some filetypes
" (to avoid which can cause floating-window related errors)
let g:scrollview_excluded_filetypes = ['vim-plug', 'GV', 'git', 'neo-tree']

" ---------------------------------------------------------------- }}}
" pretty-fold {{{

" Note: deprecated in favor of nvim-ufo
" (see ~/.config/nvim/lua/config/folding.lua)

let g:PlugConfig['pretty-fold.nvim'] = 's:init_pretty_fold'
function! s:init_pretty_fold() abort
  autocmd User LazyInit  call s:setup_prettyfold()
endfunction
function! s:setup_prettyfold() abort
lua << EOF
  -- Configuration
  -- https://github.com/anuvyklack/pretty-fold.nvim#foldtext-configuration
  local number_of_folded_lines = function()
    return string.format('%3d lines', vim.v.foldend - vim.v.foldstart + 1)
  end
  local rep_fill_char = function(times)
    return function(config) return config.fill_char:rep(times) end
  end
  require("pretty-fold").setup {
    fill_char = '╶',
    sections = {
      left = {
        'content',
      },
      right = {
         ' ', 'number_of_folded_lines', ' ', rep_fill_char(3),
      }
    },
    process_comment_signs = false,
  }

  -- Preview closed folds
  -- https://github.com/anuvyklack/pretty-fold.nvim#preview
  -- Note: help 'foldopen' to specify when fold will be opened upon actions
  require('pretty-fold.preview').setup {
    border = 'none',

    -- Disable default keybindings (h/l) which are confusing. Use `zp` instead
    key = false,
    default_keybindings = false,
  }

  _G.PreviewFold = function()
    require('pretty-fold.preview').show_preview()
  end
EOF
  command! -nargs=0 PreviewFold :lua _G.PreviewFold()
  nnoremap zp <cmd>PreviewFold<CR>
endfunction

" ---------------------------------------------------------------- }}}
" UltiSnips {{{
let g:UltiSnipsExpandTrigger = '<c-j>'
let g:UltiSnipsJumpForwardTrigger = '<c-j>'
let g:UltiSnipsJumpBackwardTrigger = '<c-k>'

" edit snippets: split the window vertically or horizontally
let g:UltiSnipsEditSplit = 'context'
let g:UltiSnipsSnippetDirectories = [$HOME . '/.config/nvim/UltiSnips']

command! -nargs=0 UltiSnipsRefreshSnippets call UltiSnips#RefreshSnippets()
            \ | call VimNotify("Reloaded Snippets.", 'info', {'title': 'Ultisnips'})
command! -nargs=0 RefreshUltiSnips         UltiSnipsRefreshSnippets

augroup UltiSnipsAutoReload
  autocmd!
  autocmd BufWritePost *.snippets :UltiSnipsRefreshSnippets
augroup END

call CommandAlias('SnippetEdit', 'UltiSnipsEdit', v:true)
call CommandAlias('EditSnippets', 'UltiSnipsEdit', v:true)
call CommandAlias('USE', 'UltiSnipsEdit', v:false)

" ---------------------------------------------------------------- }}}
" vim-pandoc {{{

let g:PlugConfig['vim-pandoc'] = 's:init_pandoc'
function! s:init_pandoc() abort

  " disable automatic folding
  let g:pandoc#modules#disabled = ['folding']

  " disable conceals
  let g:pandoc#syntax#conceal#use = 0

  " disable spell check
  let g:pandoc#spell#enabled = 0

  " Do not map <leader>{nr, rg, rb} by default
  let g:pandoc#keyboard#use_default_mappings = 0

endfunction

" ---------------------------------------------------------------- }}}
" vim-verdict {{{

" cooperative mode required to make it work with LSP engines, YCM, etc.
let g:Verdin#cooperativemode = 1

" ---------------------------------------------------------------- }}}
" echodoc.vim {{{
let g:echodoc#enable_at_startup = 1

" ---------------------------------------------------------------- }}}
" semshi {{{

" Disable diagonostics sign in favor of LSP.
let g:semshi#error_sign = 0

" ---------------------------------------------------------------- }}}
" fugitive {{{
" See also: config/git.lua

let g:fugitive_legacy_commands = 0

" command aliases
command! -nargs=* GCommit   call s:GCommit('<mods>', <q-args>)
function! s:GCommit(mods, args) abort
  let l:mods = empty(a:mods) ? "tab" : a:mods   " defaults to a new tab
  exec printf("%s Git commit -v %s", l:mods, a:args)
endfunction

" Remove or replace some fugitive commands
augroup FugitiveCustomizeCommands
  autocmd CmdlineEnter * ++once silent! delcommand GDelete

 " :G         => :GDiff
 " :G <args>  => :Git <args>
  autocmd CmdlineEnter * ++once command! -nargs=?  -complete=customlist,fugitive#Complete  G    call s:G(<q-args>)
augroup END

function! s:G(arg) abort
  if empty(Trim(a:arg))
    GDiff
  else
    exec 'Git ' . a:arg
  endif
endfunction


" fugitive key mappings
nnoremap <leader>gd :Gvdiff<CR>
nnoremap <leader>gD :GitThreeWayDiff<CR>
nnoremap <leader>gw :Gwrite<CR>
nnoremap <silent> <leader>gb :echo ':Git blame -w'<CR>:Git blame -w<CR>:call FugitiveResizeGblame()<CR>
function! FugitiveResizeGblame() abort
  if &ft == 'fugitiveblame' | vertical resize 10 | endif
endfunction

" on commit, type 'cA' to enter in amend mode
au FileType gitcommit nnoremap <buffer> <silent>
            \ cA :bd<CR>:<C-U>Git commit --verbose --amend<CR>

" Git: more configuration and keymaps for fugitive index buffers
augroup FugitiveAddon
  autocmd!

  " [?] show help  / [ci] commit -v
  autocmd FileType fugitive    nnoremap <buffer> <silent> ?  :<C-u>vertical help fugitive-maps<CR>:wincmd p<CR>
  autocmd FileType fugitive    nmap     <buffer> <silent> g? ?
  autocmd FileType fugitive    nmap     <buffer> ci          cvc
  " [g=] Toggle inline diffs for all files
  " [space] same as = (toggle inline diffs for the current file)
  autocmd FileType fugitive    nmap     <buffer> g=          ggVG=zM
  autocmd FileType fugitive    nmap     <buffer> <space>     =
  autocmd FileType fugitive    nmap     <buffer> <tab>       =
  " [,gd] diffvsplit
  autocmd FileType fugitive    nmap     <buffer> <leader>gd  dv

augroup END

function! ReloadFugitiveBlobs(...) abort
  WinDo if bufname('%') =~ '^fugitive://' | :e | endif
endfunction

" highlights for fugitive
function! HighlightsFugitive() abort
  hi fugitiveUntrackedHeading  gui=bold  guifg=#000087    guibg=Gold1
  hi fugitiveUntrackedSection  gui=bold  guifg=#bbbb00    guibg=NONE
  hi fugitiveUnstagedHeading   gui=bold  guifg=black      guibg=#d7875f
  hi fugitiveUnstagedSection   gui=bold  guifg=#af0000    guibg=NONE
  hi fugitiveStagedHeading     gui=bold  guifg=black      guibg=SeaGreen2
  hi fugitiveStagedSection     gui=bold  guifg=#009900    guibg=NONE
  hi fugitiveHeading           gui=bold                   guibg=NONE
endfunction
call RegisterHighlights('HighlightsFugitive')

" fugitive://... buffers (index) should have better statusline;
" display nothing but buffer/file names (would contain SHA, or ref, etc.)
let g:PlugConfig['vim-airline'] = 's:init_airline_fugitive'   " not used
function! s:init_airline_fugitive() abort
  function! FugitiveAirlinePatch(...)
    if bufname('%') =~ '^fugitive://'
      let w:airline_section_b = airline#section#create(['branch'])
      let w:airline_section_c = '%f'
      let w:airline_section_z = ''     " no line numbers, etc.
    endif
  endfunction
  try
    call airline#remove_statusline_func('FugitiveAirlinePatch')
    call airline#add_statusline_func('FugitiveAirlinePatch')
  catch /E117/  " airline doesn't exist yet
  endtry
endfunction

" ---------------------------------------------------------------- }}}
" gitsigns.nvim {{{
" :Gitsigns

" see ~/.config/nvim/lua/config/git.lua

" ---------------------------------------------------------------- }}}
" diffview.nvim {{{
" Assumed fugitive is always available.

" see ~/.config/nvim/lua/config/git.lua

" ---------------------------------------------------------------- }}}
" git-messenger {{{

" map <C-O>/<C-I> to jumping to older and Older(recent) commits,
" respectively (see rhysd/git-messenger.vim#3)
augroup git_messenger_autocmd
  autocmd!
  autocmd FileType gitmessengerpopup nmap <buffer> <C-O> o
  autocmd FileType gitmessengerpopup nmap <buffer> <C-I> O
augroup END

" Display content diff as well in the popup window
let g:git_messenger_include_diff = 'current'

" Use git blame -w (ignore-whitespaces).
let g:git_messenger_extra_blame_args = '-w'

" Use border for the popup window.
if has('nvim-0.5.0')
lua << EOF
  vim.g.git_messenger_floating_win_opts = {
    border = 'single',
  }
EOF
endif

" ---------------------------------------------------------------- }}}
" gundo key mappings and options {{{
let g:gundo_right = 1   " show at right
nnoremap <leader>G :GundoToggle<CR>

" ---------------------------------------------------------------- }}}
" tagbar {{{

nnoremap <leader>T :TagbarToggle<CR>

" Do not display visibility symbols (+, -, ...) which are not pretty.
let g:tagbar_show_visibility = 0


" ---------------------------------------------------------------- }}}

" Source all PlugConfig for 'enabled' plugins
let g:PlugConfig = map(g:PlugConfig, 'funcref(v:val)')
function! s:source_PlugConfig() abort
  " for vim-plug
  for name in keys(g:plugs)
    if has_key(g:PlugConfig, name)
      call g:PlugConfig[name]()
    endif
  endfor
endfunction | call s:source_PlugConfig()

" }}}
"""""""""""""""""""""""""""""""""""""""""
" Extra Settings {{{
"""""""""""""""""""""""""""""""""""""""""

" Use local vimrc if available
if filereadable(expand('~/.vimrc.local'))
  source \~/.vimrc.local
endif

" }}}

" vim: set ts=2 sts=2 sw=2 foldmethod=marker:
